c# – WebClient.DownloadDataCompleted没有触发

我有一个非常奇怪的问题.我的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.

点赞