异步TPL死锁与第三方lib又名野鹅追逐

时间:2014-08-07 08:23:22

标签: c# task-parallel-library async-await

在这一天度过了非常令人沮丧和无益的一天后,我在这里寻找帮助。

我正在使用以未知方式启动网络连接的第三方库(我知道它是非托管lib的托管包装器)。它通过调用事件StatusChanged(status)让您了解连接的状态。

由于显然调用网络成本很高,我可能不需要它Service,我会注入一个AsyncLazy<Connection>,然后在必要时调用它。该服务由ParallelForEachAsync访问,Tasks是我根据this post同时处理StatusChanged(Connecting)的扩展程序。

如果顺序访问,一切都很顺利。任何并发,甚至2个并行任务都会在90%的时间内导致死锁。我知道这肯定与第三方lib如何与我的代码交互有关,因为a)我无法使用相同的结构重现效果但没有调用它,并且b)事件StatusChanged(Connected)收到的很好,在这一点我假设网络操作已经开始,我从来没有得到class Program { static void Main(string[] args) { AsyncContext.Run(() => MainAsync(args)); } static async Task MainAsync(string[] args) { var lazy = new AsyncLazy<Connection>(() => ConnectionFactory.Create()); var service = new Service(lazy); await Enumerable.Range(0, 100) .ParallelForEachAsync(10, async i => { await service.DoWork(); Console.WriteLine("did some work"); }, CancellationToken.None); } } class ConnectionFactory { public static Task<Connection> Create() { var tcs = new TaskCompletionSource<Connection>(); var session = new Session(); session.Connected += (sender, args) => { Console.WriteLine("connected"); tcs.SetResult(new Connection()); }; session.Connect(); return tcs.Task; } } class Connection { public async Task DoSomethinElse() { await Task.Delay(1000); } } class Session { public event EventHandler Connected; public void Connect() { Console.WriteLine("Simulate network operation with unknown scheduling"); Task.Delay(100).Wait(); Connected(this, EventArgs.Empty); } } class Service { private static Random r = new Random(); private readonly AsyncLazy<Connection> lazy; public Service(AsyncLazy<Connection> lazy) { this.lazy = lazy; } public async Task DoWork() { Console.WriteLine("Trying to do some work, will connect"); await Task.Delay(r.Next(0, 100)); var connection = await lazy; await connection.DoSomethinElse(); } } public static class AsyncExtensions { public static async Task<AsyncParallelLoopResult> ParallelForEachAsync<T>( this IEnumerable<T> source, int degreeOfParallelism, Func<T, Task> body, CancellationToken cancellationToken) { var partitions = Partitioner.Create(source).GetPartitions(degreeOfParallelism); bool wasBroken = false; var tasks = from partition in partitions select Task.Run(async () => { using (partition) { while (partition.MoveNext()) { if (cancellationToken.IsCancellationRequested) { Volatile.Write(ref wasBroken, true); break; } await body(partition.Current); } } }); await Task.WhenAll(tasks) .ConfigureAwait(false); return new AsyncParallelLoopResult(Volatile.Read(ref wasBroken)); } } public class AsyncParallelLoopResult { public bool IsCompleted { get; private set; } internal AsyncParallelLoopResult(bool isCompleted) { IsCompleted = isCompleted; } } 的回调。

这是一个尽可能忠实的代码结构重复,不幸的是它不能重现死锁。

关于如何解决这个问题的任何想法?

DoWork

修改

我想我理解为什么会这样,但不知道如何解决它。当上下文等待DoWork时,Connection WaitForConnection() { connectionLazy.Start(); var awaiter = connectionLazy.GetAwaiter(); while (!awaiter.IsCompleted) Thread.Sleep(50); return awaiter.GetResult(); } 正在等待延迟连接。

这个丑陋的黑客似乎解决了它:

{{1}}

更优雅的解决方案?

1 个答案:

答案 0 :(得分:1)

我怀疑第三方库需要某种STA抽取。这在旧式异步代码中非常常见。

我有一个类型AsyncContextThread你可以尝试,将true传递给构造函数以启用手动STA抽取。 AsyncContextThreadAsyncContext类似,只不过它在新线程中运行上下文(在这种情况下是一个STA线程)。

static void Main(string[] args)
{
  using (var thread = new AsyncContextThread(true))
  {
    thread.Factory.Run(() => MainAsync(args)).Wait();
  }
}

static void Main(string[] args)
{
  AsyncContext.Run(() => async
  {
    using (var thread = new AsyncContextThread(true))
    {
      await thread.Factory.Run(() => MainAsync(args));
    }
  }
}

请注意,AsyncContextThread无法在所有 STA方案中使用。我做的(一些相当扭曲的)COM互操作需要一个真正的UI线程(WPF或WinForms线程)时遇到了问题;出于某种原因,STA泵送不足以满足那些COM对象。