この問題に出くわすまで、私はasync
/await
とTask.Run()
をよく理解していたと思いました。
RecyclerView
とViewAdapter
を使用して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
メソッドを非同期にし、Task.Runの代わりにawaitを使用すると、コードがクラッシュします
imageView.SetImageBitmap(bitmap);
UIスレッドにないことを言って、それは私には完全に理にかなっています。
では、なぜTask.Run()がクラッシュしないのにasync
/await
コードがクラッシュするのでしょうか?
更新:回答
Task.Runが待機されていなかったため、スローされた例外は表示されませんでした。 Task.Runを待機している場合、予期したエラーがありました。詳細については、以下の回答をご覧ください。
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");});
}
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.htmlhttps://blog.stephencleary.com/2013/11/ taskrun-Etiquette-examples-using.html
おそらくUIアクセスは依然としてUIKitThreadAccessException
をスローします。 Task.Wait()
が返すマーカーでawait
キーワードまたはTask.Run()
を使用していないため、これは観察しません。 非同期メソッドによってスローされた例外をキャッチする StackOverflowの説明を参照してください。トピックに関するMSDNドキュメントは少し古くなっています。
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に関する議論、- TaskStatusの意味 Stephenの記事Toubおよび Iスレッドの操作 Microsoft Docsの記事。