簡體   English   中英

了解async / await和Task.Run()

[英]Understanding async / await and Task.Run()

我以為我理解了async / awaitTask.Run()直到我遇到這個問題:

我正在使用帶有ViewAdapterRecyclerView編寫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綁定方法
  • UI-Thread應該用於UI相關的方法

通過將代碼移動到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 ExceptionAndroid -在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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM