WebClient.DownloadDataCompleted not firing
本文关键字:firing not DownloadDataCompleted WebClient | 更新日期: 2023-09-27 18:20:17
我有一个非常奇怪的问题。我的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客户端是否没有被垃圾收集,以及我的事件处理程序是否仍然连接。
现在,我没有办法解决这个问题了
还有什么原因会导致这种奇怪的行为
来自注释:
稍后,会有一个Task.WaitAll等待此任务和其他任务。然而,(1)我不明白为什么这会影响异步下载-请详细说明-和(2)问题并没有消失,当我添加睡眠时,Task.WaitAll将不会被称为
您似乎有一个由Task.WaitAll
引起的死锁。我可以在这里详细解释:
当await
是一个返回Task
或Task<T>
的异步方法时,Task.GetAwaiter
方法生成的TaskAwaitable
会隐式捕获SynchronizationContext
。
一旦同步上下文就位并且异步方法调用完成,TaskAwaitable
就尝试将延续(基本上是第一个await
关键字之后的方法调用的其余部分)封送到先前捕获的SynchronizationContext
(使用SynchronizationContext.Post
)上。如果调用线程被阻塞,等待同一方法完成,则会出现死锁。
当您调用Task.WaitAll
时,您会阻塞直到所有任务完成,这将使封送回原始上下文变得不可能,并且基本上是死锁。
不要使用Task.WaitAll
,而是使用await Task.WhenAll
。
根据评论,不是一个理想的答案,但您可以临时更改foreach前后的同步上下文:
var syncContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
foreach (var file in localFiles)
{
...
}
SynchronizationContext.SetSynchronizationContext(syncContext);