首先,从 Jeffrey Richter 的 MSDN 杂志文章“实现 CLR 异步编程模型(2007 年 3 月号)”AsyncResultNoResult
中获取实现代码。AsyncResult<TResult>
一旦有了这些基类,就可以相对轻松地实现自己的异步结果。在此示例中,我将使用您的基本代码启动 Web 请求,然后将响应作为由多个内部异步操作组成的单个异步操作来获取。
// This is the class that implements the async operations that the caller will see
internal class MyClass
{
public MyClass() { /* . . . */ }
public IAsyncResult BeginMyOperation(Uri requestUri, AsyncCallback callback, object state)
{
return new MyOperationAsyncResult(this, requestUri, callback, state);
}
public WebResponse EndMyOperation(IAsyncResult result)
{
MyOperationAsyncResult asyncResult = (MyOperationAsyncResult)result;
return asyncResult.EndInvoke();
}
private sealed class MyOperationAsyncResult : AsyncResult<WebResponse>
{
private readonly MyClass parent;
private readonly HttpWebRequest webRequest;
private bool everCompletedAsync;
public MyOperationAsyncResult(MyClass parent, Uri requestUri, AsyncCallback callback, object state)
: base(callback, state)
{
// Occasionally it is necessary to access the outer class instance from this inner
// async result class. This also ensures that the async result instance is rooted
// to the parent and doesn't get garbage collected unexpectedly.
this.parent = parent;
// Start first async operation here
this.webRequest = WebRequest.Create(requestUri);
this.webRequest.Method = "POST";
this.webRequest.BeginGetRequestStream(this.OnGetRequestStreamComplete, null);
}
private void SetCompletionStatus(IAsyncResult result)
{
// Check to see if we did not complete sync. If any async operation in
// the chain completed asynchronously, it means we had to do a thread switch
// and the callback is being invoked outside the starting thread.
if (!result.CompletedSynchronously)
{
this.everCompletedAsync = true;
}
}
private void OnGetRequestStreamComplete(IAsyncResult result)
{
this.SetCompletionStatus(result);
Stream requestStream = null;
try
{
stream = this.webRequest.EndGetRequestStream(result);
}
catch (WebException e)
{
// Cannot let exception bubble up here as we are on a callback thread;
// in this case, complete the entire async result with an exception so
// that the caller gets it back when they call EndXxx.
this.SetAsCompleted(e, !this.everCompletedAsync);
}
if (requestStream != null)
{
this.WriteToRequestStream();
this.StartGetResponse();
}
}
private void WriteToRequestStream(Stream requestStream) { /* omitted */ }
private void StartGetResponse()
{
try
{
this.webRequest.BeginGetResponse(this.OnGetResponseComplete, null);
}
catch (WebException e)
{
// As above, we cannot let this exception bubble up
this.SetAsCompleted(e, !this.everCompletedAsync);
}
}
private void OnGetResponseComplete(IAsyncResult result)
{
this.SetCompletionStatus(result);
try
{
WebResponse response = this.webRequest.EndGetResponse(result);
// At this point, we can complete the whole operation which
// will invoke the callback passed in at the very beginning
// in the constructor.
this.SetAsCompleted(response, !this.everCompletedAsync);
}
catch (WebException e)
{
// As above, we cannot let this exception bubble up
this.SetAsCompleted(e, !this.everCompletedAsync);
}
}
}
}
需要注意的一些事项:
- 您不能在异步回调的上下文中引发异常。您将崩溃您的应用程序,因为没有人来处理它。相反,始终在异常情况下完成异步操作。这保证了调用者将看到 EndXxx 调用的异常,然后可以适当地处理它。
- 假设 BeginXxx 可以抛出的任何东西也可能从 EndXxx 中抛出。上面的示例假设 WebException 在任何一种情况下都可能发生。
- 在调用者执行异步循环的情况下,设置“同步完成”状态很重要。这将在调用者需要从异步回调中返回时通知调用者,以避免“堆栈潜水”。有关这方面的更多信息,请参阅 Michael Marucheck 的博客文章“ Indigo 中的异步编程”(请参阅 Stack Dive 部分)。
异步编程不是最简单的事情,但是一旦你理解了这些概念,它就会非常强大。