扩展 IAsyncResult 模式以支持多参数函数调用
如何通过创建 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
允许线程通过信号进行通信。通常,这种通信涉及一个线程必须完成的任务,然后其他线程才能继续。
当一个线程开始一项必须在其他线程继续之前完成的活动时,它会调用 Reset
将 ManualResetEvent
置于非信号状态。该线程可以被视为控制 ManualResetEvent
。调用 WaitOne
在 ManualResetEvent
上的线程将阻塞,等待信号。当控制线程完成活动时,它调用 Set
来发出信号,告知等待的线程可以继续。所有等待的线程都会被释放。
一旦被信号,ManualResetEvent
就会保持信号状态,直到手动重置。也就是说,对 WaitOne
的调用会立即返回。
您可以通过将布尔值传递给构造函数来控制 ManualResetEvent
的初始状态:如果初始状态为信号状态,则为 true
,否则为 false
。
ManualResetEvent
也可以与静态 WaitAll
和 WaitAny
方法一起使用。
工作原理
主函数
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[]
。
以下是我需要做的所有事情
- 重载函数
GetActivities()
以接受所有参数作为object[]
类型。 - 在
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;
- 在
EndGetActivities
中,我们输入以下内容return (((AsynchronousResult<object[], T_ ACTIVITIESRow []>)result).FetchResultsFromAsyncOperation());
就是这样。
一旦我们实现了 IAsyncResult
接口,我们就可以轻松地实现所需的功能。
我希望这对您有所帮助。尽管我知道这可能需要更多的解释。但是,我随时欢迎您的所有问题。
结论
实现 IAsyncResult
接口并使用它将使您能够提供一种简单可靠的方式来异步调用方法,并构建具有良好性能的健壮应用程序。
链接
本文基于以下文章
其他有用链接
- http://www.sajay.com/page/Implementing-IAsyncResult.aspx
- http://esskar.wordpress.com/2009/06/30/implementing-iasyncresult-aka-namedpipeclientstream-beginconnect/
- http://msmvps.com/blogs/luisabreu/archive/2009/06/15/multithreading-implementing-the-iasyncresult-interface.aspx
- https://codeproject.org.cn/KB/dotnet/async_pattern.aspx#_Toc140900671
- http://www.code-magazine.com/article.aspx?quickid=0305071&page=1