我对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;
}
不幸的是,如果我以这种方式(通过异步/等待)使用Task.WaitAll
,则会陷入僵局。我的函数完成了自己的工作(因此它们可以正确执行),但是Task.WaitAll
永远呆在这里,因为显然ReadImagesAsync不会返回给调用者。
注释行是实际上正确运行的方法。如果我在tasks.Add(ReadImagesAsync(tag));
行中注释并使用tasks.Add(Task.Run(() => ReadImages(tag)));
-一切正常。
我在这里想念什么?
ReadImages方法看起来像这样:
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);
}
}
}
}
答案 0 :(得分:7)
您正在同步等待任务完成。没有一点WPF
的魔法,这对ConfigureAwait(false)
来说是行不通的。这是一个更好的解决方案:
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;
}
如果这是WPF,那么我确定您会在发生某种事件时调用它。您应该从事件处理程序中调用此方法,例如:
private async void OnWindowOpened(object sender, EventArgs args)
{
await Execute();
}
看着问题的编辑版本,我发现实际上您可以通过使用 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);
//...
}
现在,tasks.Add(Task.Run(() => ReadImages(tag)));
怎么了?
这需要了解SynchronizationContext
。创建任务时,您将复制安排任务的线程状态,以便在等待完成后可以返回到该状态。当您调用不带Task.Run
的方法时,您会说“我想回到UI线程”。这是不可能的,因为UI线程已经在等待任务,因此它们都在等待自己。当您将其他任务添加到组合中时,您说的是:“ UI线程必须安排一个'外部'任务,该任务将安排另一个我将要返回的'内部'任务。”
答案 1 :(得分:1)
使用WhenAll
代替WaitAll
,将Execute
变成async Task
,然后await
Task.WhenAll
返回的任务。
这样,它永远不会阻塞异步代码。
答案 2 :(得分:0)
我找到了一些更详细的文章来解释为什么实际上在这里发生死锁:
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
简短的回答是对我的异步方法进行了小的更改,因此看起来像这样:
private async Task ReadImagesAsync(string HTMLtag)
{
await Task.Run(() =>
{
ReadImages(HTMLtag);
}).ConfigureAwait(false);
}
是的。而已。突然之间,它不会死锁。但是这两篇文章+ @FCin响应解释了为什么它实际上发生了。
答案 3 :(得分:-1)
这就像您是说我不在乎ReadImagesAsync()何时完成,但是您必须等待....这是definition
Task.WaitAll
会阻塞当前线程,直到所有其他任务完成执行为止。
Task.WhenAll
方法用于创建一个任务,该任务将在且仅当所有其他任务均已完成时才完成。因此,如果您使用的是
Task.WhenAll
,则会得到一个不完整的任务对象。但是,它不会阻塞并且将允许程序执行。相反,Task.WaitAll方法调用实际上会阻塞并等待所有其他任务完成。本质上,
Task.WhenAll
将为您提供尚未完成的任务,但是您可以在指定任务完成执行后立即使用ContinueWith
。请注意,Task.WhenAll
方法和Task.WaitAll
都不会运行任务,即,任何一种方法都不会启动任何任务。
Task.WhenAll(taskList).ContinueWith(t => {
// write your code here
});