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客户端是否没有被垃圾收集,以及我的事件处理程序是否仍然连接。

现在,我没有办法解决这个问题了
还有什么原因会导致这种奇怪的行为

WebClient.DownloadDataCompleted not firing

来自注释:

稍后,会有一个Task.WaitAll等待此任务和其他任务。然而,(1)我不明白为什么这会影响异步下载-请详细说明-和(2)问题并没有消失,当我添加睡眠时,Task.WaitAll将不会被称为

您似乎有一个由Task.WaitAll引起的死锁。我可以在这里详细解释

await是一个返回TaskTask<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);