在C# 5.0中,为了更方便地异步编程,引入了async和await两个关键字。但是这造成了一定的麻烦,因为在WinForms,WPF,XXXXXXX中,返回的Task只能await,不能Wait()或者Result,否则会导致死锁。
async的返回值只能通过await等待,而await又要求父方法是async的,这是一个循环,这会导致哪里调用了async方法,async传染到哪里。有时候我们必须调用async方法,但是却不想让自己写的方法也成为async方法,怎么办呢?
一个方法是对执行上下文(SynchronizationContext)动手脚,方便起见,这里写了个Helper。注意,由于操作了全局上下文,因此不再是线程安全的。
另一个方法,是使用Task的ContinueWith方法设置回调函数,代替await的功能。这种方法只是避免了async关键字传染,并没有改变异步方法的本质。
第三种方法,是查阅async方法的源代码(必要且可能时,修改它),确保每一处都调用了ConfigureAwait(false),也就是不会阻塞主线程,然后对返回的Task使用Wait()或Result进行等待。
现在我们只讨论第一种,使用方法如下:
async Task<int> DelayAsync(int n)
{
await Task.Delay(n);
return n;
}
void Button1Click(object sender, EventArgs e)
{
int n = AsyncHelper.AsyncInline.Run(async () => {
return await DelayAsync(1000);
});
MessageBox.Show(n.ToString());
}</int>
AsyncHelper代码如下:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncHelper
{
public static class AsyncInline
{
// 使用方法:AsyncInline.Run(async () => { ... });
public static void Run(Func<task> item)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
synch.Post(async _ =>
{
try
{
await item();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
}
// 使用方法:var retval = AsyncInline.Run(async () => { ... });
public static T Run<t>(Func<task<t>> item)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
T ret = default(T);
synch.Post(async _ =>
{
try
{
ret = await
item();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
return ret;
}
private class ExclusiveSynchronizationContext : SynchronizationContext
{
private bool done;
public Exception InnerException { get; set; }
readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
readonly Queue<tuple<sendorpostcallback, object>> items =
new Queue<tuple<sendorpostcallback, object>>();
public override void Send(SendOrPostCallback d, object state)
{
throw new NotSupportedException("We cannot send to our same thread");
}
public override void Post(SendOrPostCallback d, object state)
{
lock (items)
{
items.Enqueue(Tuple.Create(d, state));
}
workItemsWaiting.Set();
}
public void EndMessageLoop()
{
Post(_ => done = true, null);
}
public void BeginMessageLoop()
{
while (!done)
{
Tuple<sendorpostcallback, object> task = null;
lock (items)
{
if (items.Count > 0)
{
task = items.Dequeue();
}
}
if (task != null)
{
task.Item1(task.Item2);
if (InnerException != null) // the method threw an exeption
{
throw new AggregateException("AsyncInline.Run method threw an exception.",
InnerException);
}
}
else
{
workItemsWaiting.WaitOne();
}
}
}
public override SynchronizationContext CreateCopy()
{
return this;
}
}
}
}</sendorpostcallback,></tuple<sendorpostcallback,></tuple<sendorpostcallback,></task<t></t></task>