65.9K
CodeProject 正在变化。 阅读更多。
Home

扩展 IAsyncResult 模式以支持多参数函数调用

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (7投票s)

2010 年 3 月 23 日

CPOL

5分钟阅读

viewsIcon

36184

如何通过创建 begin/end 对来使方法具有异步调用能力,类似于 WSDL.exe 工具为 Web 服务生成契约文件的方式。

引言

.NET 框架编程中有趣的功能之一是能够轻松使用异步编程和多线程。 .NET 提供了多种异步编程和处理线程的方法,但在 .NET 2.0 中这一点得到了极大的简化。使用 Thread 和 ThreadStart 委托等类,在 .NET 1.0 和 .NET 1.1 中构建多线程应用程序已经非常方便。ThreadPool 类在多线程应用程序中管理线程很有用。

一瞥之下,我们可以看到 BackgroundWorker 类的添加丰富了 Windows 应用程序的工具集。

我们可以通过添加属性来为 ASP.NET 页面执行异步回调

<%@ Page Async="true" ... %>

这使得用户(即使是初学者)也能轻松使用这些功能。

我将不讨论如何在 .NET 中使用多线程,这超出了本文的范围,网上有很多此类文章。

我将讨论如何通过创建 begin/end 对来使方法具有异步调用能力,类似于 WSDL.exe 工具为 Web 服务生成契约文件的方式。当您创建服务或类似的服务并希望其他人以异步方式使用它时,您将需要此功能,以便于他们实现并提高性能,而无需他们创建更多线程并进行管理等。

背景

在我从头开始开发智能客户端应用程序时,我对这个主题产生了浓厚的兴趣。最初,我创建了一个 Web 服务并编写了其所有功能。然后,我构建了一个 Windows 客户端应用程序,该应用程序使用了该服务的功能,为了提高性能,我以异步方式使用了该服务的 Begin/End 对方法。

显然,我并不关心这些函数是如何工作的或它们是如何实现的,我只需要正确调用它们,传递正确的参数,并获得正确的结果。问题出现在我需要实现相同的功能时 - 我正在使用的 Web 服务中的功能 - 以便在离线模式下工作。在这种情况下,我必须创建自己的 Begin/End 对或方法集,以便在 Windows 客户端应用程序处于离线模式运行时可以使用它们。

IAsyncResult 接口

首先,让我们快速看一下这个著名的接口。

public interface IAsyncResult{
  object AsyncState { get; }
  WaitHandle AsyncWaitHandle { get; }
  bool CompletedSynchronously { get; } bool IsCompleted { get; } 
}

正如我们所见,它**只有**四个需要实现的属性。

  • AsyncState:最简单,用于保存状态参数。
  • WaitHandle 是一个重要属性,它获取 WaitHandle 对象。
  • CompletedSynchronously 指示方法是否是同步执行的。但在我们的情况下,它始终返回 false;因为它使用线程池,所以它将始终异步执行。
  • IsCompleted 指示执行是否已完成或尚未完成。

通过浏览互联网,您可以找到一些关于此主题的好文章。但是,我最欣赏的一篇是由 luisabreu 撰写的(http://csharpfeeds.com/post/11390/Multithreading_implementing_the_IAsyncResult_interface),他写了一篇关于实现该接口的精彩文章,我在其中添加了一些内容并进行了一些调整,以使其适合我的代码。

实现 IAsyncResult 接口并异步调用函数

这是 luisabreu 的代码,并加了我的一点补充

internal class AsynchronousResult<T,TResult>: IAsyncResult 
{ 
   private volatile Boolean _isCompleted; 
   private ManualResetEvent _evt;
   private readonly AsyncCallback _cbMethod;
   private readonly Object _state;
   private TResult _result;
   private Exception _exception;
   private readonly T _Parameteres;

   public AsynchronousResult(Func<T, TResult> workToBeDone, 
          T Parameteres, AsyncCallback cbMethod, Object state)
   {

       _cbMethod = cbMethod;
       _state = state;
       _Parameteres = Parameteres;
       QueueWorkOnThreadPool(workToBeDone);
   }

   private void QueueWorkOnThreadPool(Func<T,TResult > workToBeDone) {
       ThreadPool.QueueUserWorkItem(state => {
            try {
                _result = workToBeDone( _Parameteres);
            } catch (Exception ex) {
                _exception = ex;
            } finally {
                UpdateStatusToComplete(); //1 and 2
                NotifyCallbackWhenAvailable(); //3 callback invocation
            }
       });
    }

    public TResult FetchResultsFromAsyncOperation() {
        if (!_isCompleted) {
            AsyncWaitHandle.WaitOne();
            AsyncWaitHandle.Close();
        }
        if (_exception != null) {
            throw _exception;
        }
        return _result;
    }

    private void NotifyCallbackWhenAvailable() {
        if (_cbMethod != null) {
            _cbMethod(this);
        }
    }

    public object AsyncState {
        get { return _state; }
    }

    public WaitHandle AsyncWaitHandle {
        get { return GetEvtHandle(); }
    }

    public bool CompletedSynchronously {
        get { return false; }
    }

    public bool IsCompleted {
        get { return _isCompleted; }
    }

    private readonly Object _locker = new Object();

    private ManualResetEvent GetEvtHandle() {
        lock (_locker) {
            if (_evt == null) {
                _evt = new ManualResetEvent(false);
            }         
            if (_isCompleted) {
                _evt.Set();
            }
        }
        return _evt;
    }

    private void UpdateStatusToComplete() {
        _isCompleted = true; //1. set _iscompleted to true
        lock (_locker) {
            if (_evt != null) {
                _evt.Set(); //2. set the event, when it exists
            }
        }
    }
}

private readonly T _Parameteres 是我的主要补充,它允许该模式适用于接受任意数量参数并返回值的函数。

但是,如果您想调用不返回值且不接受参数的过程,则存在另一种类似但更简单的实现,但我选择了最困难和最复杂的那个。

FetchResultsFromAsyncOperation 是一个辅助函数,它确保操作已完成,然后检查该操作引发的异常,然后返回该值或重新引发该异常。

ManualResetEvent 允许线程通过信号进行通信。通常,这种通信涉及一个线程必须完成的任务,然后其他线程才能继续。

当一个线程开始一项必须在其他线程继续之前完成的活动时,它会调用 ResetManualResetEvent 置于非信号状态。该线程可以被视为控制 ManualResetEvent。调用 WaitOneManualResetEvent 上的线程将阻塞,等待信号。当控制线程完成活动时,它调用 Set 来发出信号,告知等待的线程可以继续。所有等待的线程都会被释放。

一旦被信号,ManualResetEvent 就会保持信号状态,直到手动重置。也就是说,对 WaitOne 的调用会立即返回。

您可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态:如果初始状态为信号状态,则为 true,否则为 false

ManualResetEvent 也可以与静态 WaitAllWaitAny 方法一起使用。

工作原理

主函数

public T_ACTIVITIESRow[] GetActivities(string sqlWhere, string sqlOrder, int User_ID)
{
    if (string.IsNullOrEmpty(sqlWhere)) sqlWhere = "1=1";
    if (string.IsNullOrEmpty(sqlOrder)) sqlOrder = "TIMESTAMP";
    IQueryable<T_ACTIVITIESRow> Filtered = 
       _Ds._ACTIVITIESlst.AsQueryable().Where(sqlWhere).OrderBy(sqlOrder);
    return Filtered.ToArray();
}

重载函数

private T_ACTIVITIESRow[] GetActivities(object[] arg)
{
    if (string.IsNullOrEmpty(arg[0] as string )) arg[0] = "1=1";
    if (string.IsNullOrEmpty(arg[1] as string )) arg[1] = "TIMESTAMP";
    return GetActivities(arg[0] as string, arg[1] as string, (int)arg[2]);
}

Begin 函数的实现

IAsyncResult BeginGetActivities(string sqlWhere, string sqlOrder, 
             int User_ID, AsyncCallback callback, object asyncState)
{
    AsynchronousResult<object[], T_ACTIVITIESRow[]> ar = 
       new AsynchronousResult<object[], T_ACTIVITIESRow[]>(GetActivities, 
       new object[]{(object)sqlWhere, (object)sqlOrder, (object)User_ID }, 
                    callback, asyncState);
    return ar;
}

End 函数的完整实现

T_ACTIVITIESRow[] IllafWSSoap.EndGetActivities(IAsyncResult result)
{
    object ResultedValue = 
      ((AsynchronousResult<object[], T_ACTIVITIESRow[]>)result).AsyncState;
    return (((AsynchronousResult<object[], 
              T_ACTIVITIESRow[]>)result).FetchResultsFromAsyncOperation());
}

我有一个名为 GetActivities 的方法,它接受三个参数:前两个是 string 类型,第三个是 int 类型。由于参数是异构的,我必须将它们放入最通用的数组,即 object[]

以下是我需要做的所有事情

  1. 重载函数 GetActivities() 以接受所有参数作为 object[] 类型。
  2. BeginGetActivities 中,您需要做的就是创建一个新的 AsynchronousResult 实例并传入正确的参数。
    AsynchronousResult<object[], T_ ACTIVITIESRow []> ar = 
       new AsynchronousResult<object[], T_ ACTIVITIESRow Row[]>(
       GetActivities, new[] { (object)sqlWhere, (object)sqlOrder, 
       (object)User_ID }, callback, asyncState);
    return ar;
  3. EndGetActivities 中,我们输入以下内容
    return (((AsynchronousResult<object[], 
              T_ ACTIVITIESRow []>)result).FetchResultsFromAsyncOperation());

    就是这样。

一旦我们实现了 IAsyncResult 接口,我们就可以轻松地实现所需的功能。

我希望这对您有所帮助。尽管我知道这可能需要更多的解释。但是,我随时欢迎您的所有问题。

结论

实现 IAsyncResult 接口并使用它将使您能够提供一种简单可靠的方式来异步调用方法,并构建具有良好性能的健壮应用程序。

链接

本文基于以下文章

其他有用链接

© . All rights reserved.