我有一个非常奇怪的问题.我的WebClient.DownloadDataCompleted大部分时间都不会触发.
我正在使用这堂课:
public class ParallelFilesDownloader
{
public Task DownloadFilesAsync(IEnumerable<Tuple<Uri, Stream>> files, CancellationToken cancellationToken)
{
var localFiles = files.ToArray();
var tcs = new TaskCompletionSource<object>();
var clients = new List<WebClient>();
cancellationToken.Register(
() =>
{
// Break point here
foreach (var wc in clients.Where(x => x != null))
wc.CancelAsync();
});
var syncRoot = new object();
var count = 0;
foreach (var file in localFiles)
{
var client = new WebClient();
client.DownloadDataCompleted += (s, args) =>
{
// Break point here
if (args.Cancelled)
tcs.TrySetCanceled();
else if (args.Error != null)
tcs.TrySetException(args.Error);
else
{
var stream = (Stream)args.UserState;
stream.Write(args.Result, 0, args.Result.Length);
lock (syncRoot)
{
count++;
if (count == localFiles.Length)
tcs.TrySetResult(null);
}
}
};
clients.Add(client);
client.DownloadDataAsync(file.Item1, file.Item2);
}
return tcs.Task;
}
}
当我在LINQPad中单独调用DownloadFilesAsync时,正如预期的那样,在半秒左右后调用DownloadDataCompleted.
但是,在我的实际应用程序中,它根本不会触发,等待它完成的代码就会被卡住.我有两个断点,如评论所示.他们都没有被击中.
啊,但有时,它会起火.相同的URL,相同的代码,只是一个新的调试会话.完全没有模式.
我检查了线程池中的可用线程:workerThreads> 30k,completionPortThreads = 999.
我在返回之前添加了10秒的睡眠,并在睡眠后检查我的Web客户端没有被垃圾收集并且我的事件处理程序仍然附加.
现在,我没有想法来解决这个问题.
还有什么可能导致这种奇怪的行为?
最佳答案 来自评论:
Somewhere later, there is a Task.WaitAll which waits for this and other tasks. However, (1) I fail to see why this would affect the async download – please elaborate – and (2) the problem doesn’t disappear, when I add the sleep and as such, Task.WaitAll will not be called
看来你有一个由Task.WaitAll引起的死锁.我可以通过here解释它:
当您等待返回任务或任务< T>的异步方法时,由Task.WetAwaiter方法生成的TaskAwaitable会隐式捕获SynchronizationContext.
一旦该同步上下文到位并且异步方法调用完成,TaskAwaitable就会尝试将继续(基本上是第一个await关键字之后的其余方法调用)编组到SynchronizationContext上(使用SynchronizationContext.Post),这是先前捕获的.如果调用线程被阻塞,等待相同的方法完成,则会出现死锁.
当您调用Task.WaitAll时,您将阻止所有任务完成,这将使编组回到原始上下文不可能,并且基本上是死锁.
而不是使用Task.WaitAll,使用await Task.WhenAll.