![](/img/trans.png)
[英]Task.Run( () MethodName()) and await Task.Run(async () => MethodName())
[英]Understanding async / await and Task.Run()
我以為我理解了async
/ await
和Task.Run()
直到我遇到這個問題:
我正在使用帶有ViewAdapter
的RecyclerView
編寫Xamarin.Android應用程序。 在我的OnBindViewHolder方法中,我嘗試異步加載一些圖像
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
// Some logic here
Task.Run(() => LoadImage(postInfo, holder, imageView).ConfigureAwait(false));
}
然后,在我的LoadImage函數中,我做了類似的事情:
private async Task LoadImage(PostInfo postInfo, RecyclerView.ViewHolder holder, ImageView imageView)
{
var image = await loadImageAsync((Guid)postInfo.User.AvatarID, EImageSize.Small).ConfigureAwait(false);
var byteArray = await image.ReadAsByteArrayAsync().ConfigureAwait(false);
if(byteArray.Length == 0)
{
return;
}
var bitmap = await GetBitmapAsync(byteArray).ConfigureAwait(false);
imageView.SetImageBitmap(bitmap);
postInfo.User.AvatarImage = bitmap;
}
這段代碼有效 。 但為什么?
我所學到的,在configure await設置為false之后,代碼不會在SynchronizationContext
(這是UI線程)中運行。
如果我使OnBindViewHolder
方法異步並使用await而不是Task.Run,代碼崩潰
imageView.SetImageBitmap(bitmap);
說它不在UI線程中,這對我來說非常有意義。
那么為什么async
/ await
代碼崩潰而Task.Run()沒有呢?
更新:答案
由於沒有等待Task.Run,因此未顯示拋出的異常。 如果我等待Task.Run,那就是我預料到的錯誤。 進一步的解釋見下面的答案。
Task.Run()
和UI線程應該用於不同的目的:
Task.Run()
應該用於CPU綁定方法 。 通過將代碼移動到Task.Run()
,可以避免阻止UI線程。 這可能會解決您的問題,但這不是最佳做法,因為它對您的表現有害。 Task.Run()
阻塞線程池中的線程。
你應該做的是在UI線程上調用你的UI相關方法。 在Xamarin中,您可以使用Device.BeginInvokeOnMainThread()
在UI線程上運行東西:
// async is only needed if you need to run asynchronous code on the UI thread
Device.BeginInvokeOnMainThread(async () =>
{
await LoadImage(postInfo, holder, imageView).ConfigureAwait(false)
});
即使你沒有在UI線程上顯式調用它,它的工作原因可能是因為Xamarin以某種方式檢測到它應該在UI線程上運行並將此工作轉移到UI線程。
以下是Stephen Cleary的一些有用的文章,它幫助我寫了這個答案,它將幫助您進一步理解異步代碼:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html
它就像你沒有等待Task.Run一樣簡單,所以異常會被吃掉而不會返回到Task.Run的調用站點。
在Task.Run前面添加“await”,你就會得到異常。
這不會導致您的應用程序崩潰:
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() => { throw new Exception("Hello");});
}
但是這會使您的應用程序崩潰:
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() => { throw new Exception("Hello");});
}
可能UI訪問仍然會拋出UIKitThreadAccessException
。 您沒有觀察它,因為您沒有在Task.Run()
返回的標記上使用await
關鍵字或Task.Wait()
。 請參閱關於StackOverflow上的異步方法討論引發的異常 ,關於該主題的MSDN文檔有點過時。
您可以將continuation附加到Task.Run()
返回的標記,並檢查在傳遞的操作中拋出的異常:
Task marker = Task.Run(() => ...);
marker.ContinueWith(m =>
{
if (!m.IsFaulted)
return;
// Marker Faulted status indicates unhandled exceptions. Observe them.
AggregateException e = m.Exception;
});
通常,來自非UI線程的UI訪問可能會使應用程序不穩定或崩潰,但無法保證。
有關更多信息,請參閱如何處理Task.Run Exception , Android -在StackOverflow上進行異步任務討論的問題,Stephen Toub 的TaskStatus文章的含義以及使用 Microsoft Docs上的UI Thread文章。
Task.Run
被排隊LoadImage
執行與線程池的異步處理ConfigureAwait(false)
。 不會等待LoadImage
返回的任務,我相信這是重要的部分。
所以Task.Run
的結果是它立即返回一個Task<Task>
,但是外部任務沒有設置ConfigureAwait(false)
,所以整個事情在主線程上解析。
如果您將代碼更改為
Task.Run(async () => await LoadImage(postInfo, holder, imageView).ConfigureAwait(false));
我期待你點擊線程沒有在UI線程上運行的錯誤。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.