我第一次尝试进行异步编程,但是它没有像我预期的那样工作。 我有一个加载网址集的按钮(这是从代码片段中省略的)
private async void btnLoad_Click(object sender, EventArgs e)
{
foreach (var item in myCollectionOfUrls)
{
Uri tempUri = new Uri(item);
Uri = tempUri; // Uri is a property
string htmlCode = await LoadHtmlCodeAsync(Uri);
LoadAllChaptersAsync(htmlCode, Path.GetFileNameWithoutExtension(item));
}
}
LoadHtmlCodeAsync(Uri)按预期工作:
private string LoadHtmlCode(string url)
{
using (WebClient client = new WebClient())
{
try
{
System.Threading.Thread.Sleep(0);
client.Encoding = Encoding.UTF8;
client.Proxy = null;
return client.DownloadString(url);
}
catch (Exception ex)
{
Logger.Log(ex.Message);
throw;
}
}
}
但是LoadAllChaptersAsync
会抛出一个错误“这个异步方法缺少等待运算符......”
private async void LoadAllChaptersAsync(string htmlCode, string mangaName)
{
HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();
htmlDoc.LoadHtml(htmlCode);
var chapterLink = htmlDoc.DocumentNode.SelectNodes(@"//div[@id='chapterlist']//a/@href");
var chapterName = htmlDoc.DocumentNode.SelectNodes(@"//div[@id='chapterlist']//a/@href/following-sibling::text()[1]").Reverse().ToList();
for (int i = 0; i < chapterLink.Count; i++)
{
var link = "http://" + Uri.Host + chapterLink[i].GetAttributeValue("href", "not found");
var chName = chapterName[i].OuterHtml.Replace(" : ", "");
var chapterNumber = chapterLink[i].InnerText;
Chapters.Add(new Tuple<string, string, string, string>(link, chName, chapterNumber, mangaName));
}
}
我的预期结果是,在完成从html源代码中提取所需信息后,将填充Chapters(包含元组的List类型的属性)。我想以异步方式执行此操作,因为对于大量的URL,此过程可能需要一段时间,我不想阻止UI线程(它是一个Windows窗体应用程序)。
我做错了什么?
答案 0 :(得分:3)
但是
LoadAllChaptersAsync
会抛出错误:this async method lacks await operators...
这是因为您的LoadAllChaptersAsync
方法不会执行任何异步操作,也不会await
任何异步操作。
一个常见的误解是,在方法中使用async
(或await
)关键字会以某种方式神奇地在不同的线程上创建新任务。它没有。
我想以异步方式执行此操作,因为对于大量网址,此过程可能需要一段时间,我不想阻止UI线程(它是一个Windows窗体应用程序)。
您可以更改您的方法以返回在后台执行工作的新Task
,这将返回包含所有新创建的&#34;章节的新列表&#34;完成任务。如:
private Task<List<Tuple<string, string, string, string>>>
LoadAllChaptersAsync(string htmlCode, string mangaName)
{
return Task.Run(() {
var newChapters = new List<Tuple<string, string, string, string>>();
// ...
return newChapters;
});
}
可以等待此任务,无需将您的方法标记为async
不执行任何异步操作。
var newChapters = await LoadAllChaptersAsync(htmlCode, Path.GetFileNameWithoutExtension(item));
Chapters.AddRange(newChapters);
其他改进
我们可以对上述解决方案进行两项改进。我们可以为主要受CPU限制且其实现不包括异步/等待的任务合并一些最佳实践。
CancellationToken
来支持CancellationTokenSource
。对于您的代码,您可能希望提供&#34;停止加载&#34;用户界面中的按钮,单击时,使用以下内容取消LoadAllChaptersAsync
方法中执行的工作:
private async void btnStopLoading_Click(object sender, EventArgs e)
{
if (_loadChaptersCancelSource != null)
_loadChaptersCancelSource.Cancel();
}
然后您的原始代码可以更改为:
private async void btnLoad_Click(object sender, EventArgs e)
{
if (_loadChaptersCancelSource == null)
{
var wasCancelled = false;
_loadChaptersCancelSource = new CancellationTokenSource();
try
{
var token = _loadChaptersCancelSource.Token;
foreach (var item in myCollectionOfUrls)
{
// stop if cancellation was requested.
token.ThrowIfCancellationRequested();
Uri tempUri = new Uri(item);
Uri = tempUri; // Uri is a property
// also modified to be cancellable.
string htmlCode = await LoadHtmlCodeAsync(Uri, token);
// client decides to run as a background task
var newChapters = await Task.Run(() =>
LoadAllChapters(htmlCode, Path.GetFileNameWithoutExtension(item), token),
token);
Chapters.AddRange(newChapters);
}
}
catch (OperationCanceledException)
{
wasCancelled = true;
}
catch (AggregateException ex)
{
if (!ex.InnerExceptions.Any(e => typeof(OperationCanceledException).IsAssignableFrom(e.GetType())))
throw; // not cancelled, different error.
wasCancelled = true;
}
finally
{
var cts = _loadChaptersCancelSource;
_loadChaptersCancelSource = null;
cts.Dispose();
}
if (wasCancelled)
; // Show a message ?
}
}
您的LoadAllChapters
可以是常规同步方法,允许合作取消:
private List<Tuple<string, string, string, string>>
LoadAllChapters(string htmlCode, string mangaName, CancellationToken cancelToken)
{
HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();
htmlDoc.LoadHtml(htmlCode);
// Don't continue if cancelation is requested
cancelToken.ThrowIfCancellationRequested();
var chapterLink = htmlDoc.DocumentNode.SelectNodes(@"//div[@id='chapterlist']//a/@href");
var chapterName = htmlDoc.DocumentNode.SelectNodes(@"//div[@id='chapterlist']//a/@href/following-sibling::text()[1]").Reverse().ToList();
var newChapters = new List<Tuple<string, string, string, string>>();
for (int i = 0; i < chapterLink.Count; i++)
{
// Stop the loop if cancellation is requested.
cancelToken.ThrowIfCancellationRequested();
var link = "http://" + Uri.Host + chapterLink[i].GetAttributeValue("href", "not found");
var chName = chapterName[i].OuterHtml.Replace(" : ", "");
var chapterNumber = chapterLink[i].InnerText;
newChapters.Add(new Tuple<string, string, string, string>(link, chName, chapterNumber, mangaName));
}
return newChapters;
}
可以在此处找到一个非常类似的方法(确实涉及异步操作)以及一些其他解释:"Async Cancellation: Bridging between the .NET Framework and the Windows Runtime"。
答案 1 :(得分:1)
当您使用ant jar
时,您并没有立即返回代表其工作的async
的方法 - 相反,Task
方法将返回{{1}当您使用async
运算符时,表示其余工作。正如Alex的回答中所提到的,这可以通过Task
来完成,但也可以通过await
Task.Yield()
函数在一个方法中完成,该函数会立即返回。
请注意,在UI应用程序中,您通常会将Task.Run
设置为仅使用一个帖子 - 可能需要使用ConfigureAwait
来确保您使用其他线程。因此:
await
这只是一种可能性 - 最好通过拨打Thread.CurrentThread
并检查ManagedThreadId
进行测试,以确保您处于某个特定线程或其他线程上。