[英]async void method lacks await
我正在嘗試第一次進行異步編程,但是它沒有按我期望的那樣工作。 我有一個按鈕可以加載一組網址(此代碼段中省略了)
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(包含Tuple的List類型的屬性)。 我想異步執行此操作,因為對於大量的url,此過程可能需要一段時間,並且我不想阻塞UI線程(這是Windows窗體應用程序)。
我做錯了什么?
但是
LoadAllChaptersAsync
引發錯誤:this async method lacks await operators...
那是因為您的LoadAllChaptersAsync
方法不執行任何異步操作,也不await
任何異步操作。
一個常見的誤解是在方法中使用async
(或await
)關鍵字以某種方式神奇地在另一個線程上創建了一個新任務。 它不是。
我想異步執行此操作,因為對於大量的url,此過程可能需要一段時間,並且我不想阻塞UI線程(這是Windows窗體應用程序)。
您可以更改方法以返回在后台執行工作的新Task
,並且在任務完成時將返回包含所有新創建的“章節”的新列表。 如:
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限制且其實現不包括異步/喚醒的任務,我們可以結合一些最佳實踐。
CancellationTokenSource
的CancellationToken
使其支持協作 CancellationTokenSource
。 對於您的代碼,您可能希望在UI中提供一個“停止加載”按鈕,單擊該按鈕時,請使用以下命令取消在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;
}
可以在這里找到非常相似的方法(確實涉及異步操作),並附帶一些其他說明: “異步取消:.NET Framework和Windows運行時之間的橋接” 。
當您使用async
,您並沒有創建一個立即返回代表其自己工作的Task
的方法-相反,當您使用await
運算符時, async
方法將返回代表其其余工作的Task
。 如Alex的回答所述,這可以通過Task.Run
完成,但是也可以通過await
Task.Yield()
函數從方法內部完成,該函數立即返回。
請注意,在UI應用程序中,通常會將SynchronizationContext
設置為僅使用一個線程-可能需要使用ConfigureAwait
來確保您位於另一個線程上。 從而:
await Task.Yield().ConfigureAwait(false);
不過,這只是一種可能-最好通過調用Thread.CurrentThread
並檢查ManagedThreadId
來進行測試,以確保您使用的是某個特定線程或另一個線程。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.