C# Task.WaitAll死锁

C# Task.WaitAll死锁,c#,wpf,asynchronous,async-await,C#,Wpf,Asynchronous,Async Await,我有一个关于Task.WaitAll的问题。起初,我尝试使用async/await获得如下内容: private async Task ReadImagesAsync(string HTMLtag) { await Task.Run(() => { ReadImages(HTMLtag); }); } private void Execute() { string tags = ConfigurationManager.AppSetting

我有一个关于Task.WaitAll的问题。起初,我尝试使用async/await获得如下内容:

private async Task ReadImagesAsync(string HTMLtag)
{
    await Task.Run(() =>
    {
        ReadImages(HTMLtag);
    });
}
private void Execute()
{
    string tags = ConfigurationManager.AppSettings["HTMLTags"];

    var cursor = Mouse.OverrideCursor;
    Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;
    List<Task> tasks = new List<Task>();
    foreach (string tag in tags.Split(';'))
    {
         tasks.Add(ReadImagesAsync(tag));
         //tasks.Add(Task.Run(() => ReadImages(tag)));
    }

    Task.WaitAll(tasks.ToArray());
    Mouse.OverrideCursor = cursor;
}
这个函数的内容无关紧要,它是同步工作的,完全独立于外部世界

我是这样使用它的:

private async Task ReadImagesAsync(string HTMLtag)
{
    await Task.Run(() =>
    {
        ReadImages(HTMLtag);
    });
}
private void Execute()
{
    string tags = ConfigurationManager.AppSettings["HTMLTags"];

    var cursor = Mouse.OverrideCursor;
    Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;
    List<Task> tasks = new List<Task>();
    foreach (string tag in tags.Split(';'))
    {
         tasks.Add(ReadImagesAsync(tag));
         //tasks.Add(Task.Run(() => ReadImages(tag)));
    }

    Task.WaitAll(tasks.ToArray());
    Mouse.OverrideCursor = cursor;
}

就像你说我不在乎ReadImagesAsync()何时完成,但你必须等待它。。。。这是一个

Task.WaitAll
阻塞当前线程,直到所有其他任务都完成执行

Task.WhenAll
方法用于创建一个任务,当且仅当所有其他任务都完成时,该任务才会完成

因此,如果您使用的是
Task.whalll
,您将得到一个未完成的任务对象。但是,它不会阻塞,并允许程序执行。相反,Task.WaitAll方法调用实际上会阻塞并等待所有其他任务完成

基本上,
Task.whalll
将为您提供一个尚未完成的任务,但您可以在指定任务完成执行后立即使用
ContinueWith
。请注意,无论是
Task.wheall
方法还是
Task.WaitAll
方法都不会运行任务,也就是说,这些方法都不会启动任何任务


使用
WhenAll
而不是
WaitAll
,将
Execute
转换为
async Task
,并
wait
等待
Task.WhenAll
返回的任务


这样它就不会阻塞异步代码

您正在同步等待任务完成。如果没有一点
ConfigureAwait(false)
magic,这对
WPF
是不起作用的。这里有一个更好的解决方案:

private async Task Execute()
{
    string tags = ConfigurationManager.AppSettings["HTMLTags"];

    var cursor = Mouse.OverrideCursor;
    Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;
    List<Task> tasks = new List<Task>();
    foreach (string tag in tags.Split(';'))
    {
         tasks.Add(ReadImagesAsync(tag));
         //tasks.Add(Task.Run(() => ReadImages(tag)));
    }

    await Task.WhenAll(tasks.ToArray());
    Mouse.OverrideCursor = cursor;
}
查看您问题的编辑版本,我可以看到,事实上,您可以通过使用异步版本的
DownloadStringAsync
,使问题变得非常漂亮:

private async Task ReadImages (string HTMLtag)
{
    string section = HTMLtag.Split(':')[0];
    string tag = HTMLtag.Split(':')[1];

    List<string> UsedAdresses = new List<string>();
    var webClient = new WebClient();
    string page = await webClient.DownloadStringAsync(Link);

    //...
}
private async Task ReadImages(字符串HTMLtag)
{
string section=HTMLtag.Split(“:”)[0];
string tag=HTMLtag.Split(“:”)[1];
List UsedAddresses=新列表();
var webClient=新的webClient();
string page=Wait webClient.DownloadStringAsync(链接);
//...
}
现在,如何处理
tasks.Add(Task.Run(()=>ReadImages(tag))


这需要了解
SynchronizationContext
。创建任务时,复制调度任务的线程的状态,以便在完成WAIT后返回到该线程。当您在没有
任务的情况下调用方法。运行
时,您会说“我想回到UI线程”。这是不可能的,因为UI线程已经在等待任务,所以它们都在等待自己。当您将另一个任务添加到组合中时,您的意思是:“UI线程必须安排一个‘外部’任务,该任务将安排另一个‘内部’任务,我将返回该任务。”

我发现了一些更详细的文章,解释了为什么这里会发生死锁:

简单的回答是在我的异步方法中做一个小的更改,使其看起来像:

private void ReadImages (string HTMLtag)
{
    string section = HTMLtag.Split(':')[0];
    string tag = HTMLtag.Split(':')[1];

    List<string> UsedAdresses = new List<string>();
    var webClient = new WebClient();
    string page = webClient.DownloadString(Link);

    var siteParsed = Link.Split('/');

    string site = $"{siteParsed[0]} + // + {siteParsed[1]} + {siteParsed[2]}";

    int.TryParse(MinHeight, out int minHeight);
    int.TryParse(MinWidth, out int minWidth);

    int index = 0;

    while (index < page.Length)
    {
        int startSection = page.IndexOf("<" + section, index);
        if (startSection < 0)
            break;

        int endSection = page.IndexOf(">", startSection) + 1;
        index = endSection;

        string imgSection = page.Substring(startSection, endSection - startSection);

        int imgLinkStart = imgSection.IndexOf(tag + "=\"") + tag.Length + 2;
        if (imgLinkStart < 0 || imgLinkStart > imgSection.Length)
            continue;

        int imgLinkEnd = imgSection.IndexOf("\"", imgLinkStart);
        if (imgLinkEnd < 0)
            continue;

        string imgAdress = imgSection.Substring(imgLinkStart, imgLinkEnd - imgLinkStart);

        string format = null;
        foreach (var imgFormat in ConfigurationManager.AppSettings["ImgFormats"].Split(';'))
        {
            if (imgAdress.IndexOf(imgFormat) > 0)
            {
                format = imgFormat;
                break;
            }
        }

        // not an image
        if (format == null)
            continue;

        // some internal resource, but we can try to get it anyways
        if (!imgAdress.StartsWith("http"))
            imgAdress = site + imgAdress;

        string imgName = imgAdress.Split('/').Last();

        if (!UsedAdresses.Contains(imgAdress))
        {
            try
            {
                Bitmap pic = new Bitmap(webClient.OpenRead(imgAdress));
                if (pic.Width > minHeight && pic.Height > minWidth)
                    webClient.DownloadFile(imgAdress, SaveAdress + "\\" + imgName);
            }
            catch { }
            finally
            {
                UsedAdresses.Add(imgAdress);
            }
        }

    }
}
private async Task ReadImagesAsync(string HTMLtag)
{
    await Task.Run(() =>
    {
        ReadImages(HTMLtag);
    }).ConfigureAwait(false);
}

是的。就这样。突然,它没有死锁。但是这两篇文章+@ FCin响应解释了它为什么会发生。

很难在不看到ReaDimaGes()的情况下调试它,但是考虑调试过程中的步骤来了解正在发生的事情。Visual Studio即使使用多线程也能很好地实现这一点。您可能不应该阻止异步代码。有关更多信息,请参阅。如果您将
Task.WaitAll
更改为
wait Task.whalll
会发生什么情况?旁注:这可能也有关系:您还应该添加运行此任务的框架(ASP.NET、WinForms、WPF、Console应用程序…),以便我们知道什么是同步上下文(如果有的话)在SO中,引用外部链接是不受欢迎的。如果该链接有有用的信息,您可以在此处编写,否则您可以将其作为注释保留。
tasks.Add(ReadImagesAsync(tag))是的,对于异步方法,它就是这样工作的。即使是WaitAll。我不必使Execute异步。@Khaine我更新了我的答案,因此您可以看到如何利用异步使其始终异步。这是“一路”编写异步代码的首选方式。也有异步版本的
WebClient.DownloadFile
,但
DownloadStringAsync
无效。它说我等不及了。WebClient.DownloadFileAsync的情况也一样,这就是为什么我让它们保持那种状态。还有:
tasks.Add(Task.Run(()=>ReadImages(tag))与WaitAll一起实际工作。只有
任务有问题。运行
包装。@Khaine你是对的。抱歉,我没有检查方法声明。是的,添加
Task.Run
并保持
Task.WaitAll
会起作用,但这不是编写异步代码的好方法。