C# Task.WaitAll死锁
我有一个关于Task.WaitAll的问题。起初,我尝试使用async/await获得如下内容: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
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
会起作用,但这不是编写异步代码的好方法。