Saya ingin menanyakan pendapat Anda tentang arsitektur yang benar kapan harus digunakan Task.Run
. Saya mengalami UI lamban dalam aplikasi WPF .NET 4.5 kami (dengan kerangka kerja Caliburn Micro).
Pada dasarnya saya lakukan (cuplikan kode yang sangat sederhana):
public class PageViewModel : IHandle<SomeMessage>
{
...
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
// Makes UI very laggy, but still not dead
await this.contentLoader.LoadContentAsync();
HideLoadingAnimation();
}
}
public class ContentLoader
{
public async Task LoadContentAsync()
{
await DoCpuBoundWorkAsync();
await DoIoBoundWorkAsync();
await DoCpuBoundWorkAsync();
// I am not really sure what all I can consider as CPU bound as slowing down the UI
await DoSomeOtherWorkAsync();
}
}
Dari artikel / video yang saya baca / lihat, saya tahu itu await
async
belum tentu berjalan di utas latar dan untuk mulai bekerja di latar belakang Anda harus membungkusnya dengan menunggu Task.Run(async () => ... )
. Menggunakan async
await
tidak memblokir UI, tetapi tetap berjalan di utas UI, sehingga membuatnya tertinggal.
Di mana tempat terbaik untuk meletakkan Task.Run?
Haruskah saya hanya
Bungkus panggilan luar karena ini kurang bekerja threading untuk .NET
, atau haruskah saya membungkus hanya metode terikat CPU yang berjalan secara internal
Task.Run
karena ini membuatnya dapat digunakan kembali untuk tempat lain? Saya tidak yakin di sini jika mulai mengerjakan utas latar belakang jauh di inti adalah ide yang bagus.
Ad (1), solusi pertama akan seperti ini:
public async void Handle(SomeMessage message)
{
ShowLoadingAnimation();
await Task.Run(async () => await this.contentLoader.LoadContentAsync());
HideLoadingAnimation();
}
// Other methods do not use Task.Run as everything regardless
// if I/O or CPU bound would now run in the background.
Ad (2), solusi kedua akan seperti ini:
public async Task DoCpuBoundWorkAsync()
{
await Task.Run(() => {
// Do lot of work here
});
}
public async Task DoSomeOtherWorkAsync(
{
// I am not sure how to handle this methods -
// probably need to test one by one, if it is slowing down UI
}
sumber
await Task.Run(async () => await this.contentLoader.LoadContentAsync());
seharusnyaawait Task.Run( () => this.contentLoader.LoadContentAsync() );
. AFAIK Anda tidak mendapatkan apa-apa dengan menambahkan sedetikawait
danasync
dalamTask.Run
. Dan karena Anda tidak melewatkan parameter, itu menyederhanakan sedikit lebih banyakawait Task.Run( this.contentLoader.LoadContentAsync );
.Jawaban:
Perhatikan pedoman untuk melakukan pekerjaan pada utas UI , dikumpulkan di blog saya:
Ada dua teknik yang harus Anda gunakan:
1) Gunakan
ConfigureAwait(false)
saat Anda bisa.Misalnya,
await MyAsync().ConfigureAwait(false);
bukannyaawait MyAsync();
.ConfigureAwait(false)
memberitahuawait
bahwa Anda tidak perlu melanjutkan pada konteks saat ini (dalam hal ini, "pada konteks saat ini" berarti "pada utas UI"). Namun, untuk sisaasync
metode itu (setelahConfigureAwait
), Anda tidak dapat melakukan apa pun yang mengasumsikan Anda berada dalam konteks saat ini (misalnya, memperbarui elemen UI).Untuk informasi lebih lanjut, lihat artikel MSDN saya Praktik Terbaik di Pemrograman Asinkron .
2) Gunakan
Task.Run
untuk memanggil metode yang terikat CPU.Anda harus menggunakan
Task.Run
, tetapi tidak dalam kode apa pun yang Anda ingin dapat digunakan kembali (yaitu, kode perpustakaan). Jadi Anda gunakanTask.Run
untuk memanggil metode, bukan sebagai bagian dari implementasi metode.Jadi pekerjaan yang terikat CPU murni akan terlihat seperti ini:
Yang akan Anda panggil menggunakan
Task.Run
:Metode yang merupakan campuran dari CPU-terikat dan I / O-terikat harus memiliki
Async
tanda tangan dengan dokumentasi menunjukkan sifat terikat-CPU mereka:Yang juga akan Anda panggil menggunakan
Task.Run
(karena sebagian terikat CPU):sumber
ConfigureAwait(false)
. Jika Anda melakukannya pertama kali, maka Anda mungkin merasa ituTask.Run
sama sekali tidak perlu. Jika Anda tidak masih perluTask.Run
, maka hal itu tidak membuat banyak perbedaan untuk runtime dalam hal ini apakah Anda menyebutnya sekali atau berkali-kali, jadi hanya melakukan apa yang paling alami untuk kode Anda.ConfigureAwait(false)
metode cpu-terikat Anda, itu masih thread UI yang akan melakukan metode cpu-terikat, dan hanya semuanya setelah itu dapat dilakukan pada thread TP. Atau apakah saya salah paham akan sesuatu?Task.Run
mengerti tanda tangan asinkron, jadi tidak akan selesai sampaiDoWorkAsync
selesai. Ekstraasync
/await
tidak perlu. Saya menjelaskan lebih banyak tentang "mengapa" dalam seri blogTask.Run
saya tentang etiket .TaskCompletionSource<T>
atau salah satu dari notasi singkatnya sepertiFromAsync
. Saya memiliki posting blog yang lebih detail mengapa metode async tidak memerlukan utas .Satu masalah dengan ContentLoader Anda adalah bahwa ia beroperasi secara berurutan. Pola yang lebih baik adalah memparalelkan pekerjaan dan kemudian melakukan sinkronisasi pada akhirnya, jadi kita dapatkan
Jelas, ini tidak berfungsi jika ada tugas yang membutuhkan data dari tugas sebelumnya yang lain, tetapi seharusnya memberikan Anda hasil keseluruhan yang lebih baik untuk sebagian besar skenario.
sumber