async/await的多线程问题
今天尝试把.net4.5新增的异步编程模型async/await加入自己的框架,因为从第一印象看,使用async/await的写法实在太方便了,以同步代码的方式写异步流程,写起来更顺畅,不容易打断思路,异常传递、资源控制(lock和using)也都完美支持,即使有少量的性能损失,也完全可以接受。
首先我写了一个测试代码,以熟悉async/await模型,代码如下:
static class TestCase
{
static async Task Test2()
{
Console.WriteLine("3 {0}", Thread.CurrentThread.ManagedThreadId);
await Task.Run(() =>
{
Thread.Sleep(1000);
});// 这里可以换成Task.Delay(1000);
Console.WriteLine("4 {0}", Thread.CurrentThread.ManagedThreadId);
}
static async Task Test1()
{
Console.WriteLine("1 {0}", Thread.CurrentThread.ManagedThreadId);
await Test2();
Console.WriteLine("2 {0}", Thread.CurrentThread.ManagedThreadId);
}
static void Main(string[] args)
{
Test1().Wait();
}
}
输出如下: 代码很简单,结果也看似正确,直到我发现一个大问题:
await后面的代码是在另一个线程执行的!!!
这不仅会影响到框架的运行,还会导致线程争用资源的问题,也就是说看似同一处的代码,会运行在不同线程,并且有可能并行。如果访问公共资源(如静态变量)还需要加锁,严重影响编程体验和性能。我推测,很可能Task类自带的创建Task的静态函数所产生的Task,设置Complete状态时都是在其他线程,因此导致了await的回调也在该线程执行。于是我把代码稍微改了一下:
static class TestCase
{
static TaskCompletionSource<object> source = new TaskCompletionSource<object>();
static async Task Test2()
{
Console.WriteLine("3 {0}", Thread.CurrentThread.ManagedThreadId);
await source.Task;
Console.WriteLine("4 {0}", Thread.CurrentThread.ManagedThreadId);
}
static async Task Test1()
{
Console.WriteLine("1 {0}", Thread.CurrentThread.ManagedThreadId);
await Test2();
Console.WriteLine("2 {0}", Thread.CurrentThread.ManagedThreadId);
}
static void Main(string[] args)
{
Task task = Test1();
Thread.Sleep(1000);
source.SetResult(null);
task.Wait(); // 这一句其实是没有必要的,并且如果放在SetResult前,会导致死锁
}
}
输出如下: 果然全部是在主线程执行了。其中source.SetResult(null);这一句放到框架中,当条件合适时就在主线程Loop中调用,以便唤醒await继续执行剩下的过程。此外,Task类的静态函数所产生的Task,也可以通过一个包装函数,来让在其他线程执行的SetResult,Queue到主线程调用,类似这样:
public Task<T> Wrap<T>(Task<T> task)
{
Loop loop = Current;
TaskCompletionSource<T> source = new TaskCompletionSource<T>();
task.GetAwaiter().OnCompleted(() =>
{
loop.Execute(() =>
{
if (task.IsCompleted)
source.TrySetResult(task.Result);
else if (task.IsCanceled)
source.TrySetCanceled();
else
source.TrySetException(task.Exception);
});
});
return source.Task;
}
最后修改于 2016-08-14