在这一天度过了非常令人沮丧和无益的一天后,我在这里寻找帮助。
我正在使用以未知方式启动网络连接的第三方库(我知道它是非托管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}}
更优雅的解决方案?
答案 0 :(得分:1)
我怀疑第三方库需要某种STA抽取。这在旧式异步代码中非常常见。
我有一个类型AsyncContextThread
你可以尝试,将true
传递给构造函数以启用手动STA抽取。 AsyncContextThread
与AsyncContext
类似,只不过它在新线程中运行上下文(在这种情况下是一个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对象。