Saya sedang mengerjakan proyek jaringan multi-tasking dan saya baru Threading.Tasks
. Saya menerapkan sederhana Task.Factory.StartNew()
dan saya bertanya-tanya bagaimana saya bisa melakukannya dengan Task.Run()
?
Ini kode dasarnya:
Task.Factory.StartNew(new Action<object>(
(x) =>
{
// Do something with 'x'
}), rawData);
Saya melihat ke System.Threading.Tasks.Task
dalam Browser Objek dan saya tidak dapat menemukan Action<T>
parameter sejenis. Hanya ada Action
yang mengambil void
parameter dan tidak ada tipe .
Hanya ada 2 hal yang serupa: static Task Run(Action action)
dan static Task Run(Func<Task> function)
tetapi tidak dapat memposting parameter dengan keduanya.
Ya, saya tahu saya bisa membuat metode ekstensi sederhana untuk itu tapi pertanyaan utama saya adalah bisa kita tulis pada baris dengan Task.Run()
?
c#
lambda
task-parallel-library
task
MFatihMAR
sumber
sumber
rawData
adalah paket data jaringan yang memiliki kelas kontainer (seperti DataPacket) dan saya menggunakan kembali contoh ini untuk mengurangi tekanan GC. Jadi, jika saya menggunakanrawData
secara langsungTask
, itu (mungkin) dapat diubah sebelumTask
menanganinya. Sekarang, saya rasa saya bisa membuatbyte[]
contoh lain untuk itu. Saya pikir ini solusi paling sederhana untuk saya.Action<byte[]>
tidak mengubah itu.Jawaban:
private void RunAsync() { string param = "Hi"; Task.Run(() => MethodWithParameter(param)); } private void MethodWithParameter(string param) { //Do stuff }
Sunting
Karena permintaan yang populer, saya harus mencatat bahwa
Task
peluncuran akan berjalan secara paralel dengan utas panggilan. Dengan asumsi defaultTaskScheduler
ini akan menggunakan .NETThreadPool
. Bagaimanapun, ini berarti Anda perlu memperhitungkan parameter apa pun yang diteruskan ke yangTask
berpotensi diakses oleh beberapa utas sekaligus, menjadikannya status bersama. Ini termasuk mengaksesnya di utas panggilan.Dalam kode saya di atas kasus itu dibuat sepenuhnya diperdebatkan. String tidak bisa diubah. Itu sebabnya saya menggunakan mereka sebagai contoh. Tetapi katakanlah Anda tidak menggunakan
String
...Salah satu solusinya adalah dengan menggunakan
async
danawait
. Ini, secara default, akan menangkapSynchronizationContext
thread pemanggil dan akan membuat kelanjutan untuk metode lainnya setelah panggilan keawait
dan melampirkannya ke yang dibuatTask
. Jika metode ini berjalan di utas WinForms GUI, itu akan menjadi tipeWindowsFormsSynchronizationContext
.Kelanjutan akan berjalan setelah diposting kembali ke yang ditangkap
SynchronizationContext
- lagi hanya secara default. Jadi Anda akan kembali ke utas yang Anda mulai setelahawait
panggilan. Anda dapat mengubahnya dengan berbagai cara, terutama dengan menggunakanConfigureAwait
. Singkatnya, sisa metode yang tidak akan berlanjut sampai setelah yangTask
telah diselesaikan pada thread lain. Tetapi utas panggilan akan terus berjalan secara paralel, tidak hanya seluruh metode.Penantian untuk menyelesaikan menjalankan sisa metode ini mungkin diinginkan atau tidak diinginkan. Jika tidak ada dalam metode itu nanti yang mengakses parameter yang diteruskan ke
Task
Anda mungkin tidak ingin menggunakanawait
sama sekali.Atau mungkin Anda menggunakan parameter tersebut nanti dalam metode. Tidak ada alasan untuk
await
segera karena Anda bisa terus melakukan pekerjaan dengan aman. Ingat, Anda bisa menyimpan yangTask
dikembalikan dalam variabel danawait
nanti - bahkan dalam metode yang sama. Misalnya, setelah Anda perlu mengakses parameter yang diteruskan dengan aman setelah melakukan banyak pekerjaan lain. Sekali lagi, Anda tidak perluawait
diTask
kanan saat Anda menjalankannya.Bagaimanapun, cara sederhana untuk membuat thread ini aman sehubungan dengan parameter yang diteruskan
Task.Run
adalah dengan melakukan ini:Anda harus terlebih dahulu mendekorasi
RunAsync
denganasync
:private async void RunAsync()
Catatan penting
Lebih disukai metode yang ditandai tidak boleh kembali kosong, seperti yang disebutkan dalam dokumentasi terkait. Pengecualian umum untuk ini adalah pengendali kejadian seperti klik tombol dan semacamnya. Mereka harus mengembalikan kekosongan. Kalau tidak, saya selalu mencoba mengembalikan atau saat menggunakan . Ini praktik yang baik karena beberapa alasan.
async
Task
Task<TResult>
async
Sekarang Anda dapat
await
menjalankanTask
seperti di bawah ini. Anda tidak dapat menggunakanawait
tanpaasync
.await Task.Run(() => MethodWithParameter(param)); //Code here and below in the same method will not run until AFTER the above task has completed in one fashion or another
Jadi, secara umum, jika Anda
await
melakukan tugas tersebut, Anda dapat menghindari memperlakukan parameter yang diteruskan sebagai sumber daya yang berpotensi dibagikan dengan semua perangkap memodifikasi sesuatu dari beberapa utas sekaligus. Juga, waspadalah terhadap penutupan . Saya tidak akan membahasnya secara mendalam tetapi artikel terkait melakukan pekerjaan dengan baik.Catatan Samping
Sedikit keluar dari topik, tapi hati-hati menggunakan semua jenis "pemblokiran" pada utas GUI WinForms karena itu ditandai dengan
[STAThread]
. Penggunaanawait
tidak akan memblokir sama sekali, tetapi terkadang saya melihatnya digunakan sehubungan dengan semacam pemblokiran."Blokir" ada dalam tanda kutip karena Anda secara teknis tidak dapat memblokir utas GUI WinForms . Ya, jika Anda menggunakan
lock
pada utas GUI WinForms, ia masih akan memompa pesan, meskipun Anda mengira itu "diblokir". Ini bukan.Ini dapat menyebabkan masalah aneh dalam kasus yang sangat jarang terjadi. Salah satu alasan Anda tidak ingin menggunakan
lock
saat melukis, misalnya. Tapi itu kasus pinggiran dan kompleks; Namun saya telah melihatnya menyebabkan masalah gila. Jadi saya mencatatnya demi kelengkapan.sumber
Task.Run(() => MethodWithParameter(param));
. Berarti bahwa jika yangparam
akan diubah setelah ituTask.Run
, Anda mungkin memiliki hasil yang tidak diharapkan padaMethodWithParameter
.Gunakan tangkapan variabel untuk "meneruskan" parameter.
var x = rawData; Task.Run(() => { // Do something with 'x' });
Anda juga dapat menggunakan
rawData
secara langsung tetapi Anda harus berhati-hati, jika Anda mengubah nilai dirawData
luar tugas (misalnya iterator dalam satufor
lingkaran) itu juga akan mengubah nilai di dalam tugas.sumber
Task.Run
.Mulai sekarang Anda juga dapat:
Action<int> action = (o) => Thread.Sleep(o); int param = 10; await new TaskFactory().StartNew(action, param)
sumber
IDisposable
objek ke delegasi tugas untuk menyelesaikan peringatan ReSharper "Variabel yang ditangkap dibuang di lingkup luar" , ini melakukannya dengan sangat baik. Berlawanan dengan kepercayaan populer, tidak ada yang salah dengan menggunakanTask.Factory.StartNew
alih-alih diTask.Run
mana Anda harus melewati keadaan. Lihat disini .Saya tahu ini adalah utas lama, tetapi saya ingin membagikan solusi yang akhirnya harus saya gunakan karena pos yang diterima masih memiliki masalah.
Masalah:
Seperti yang ditunjukkan oleh Alexandre Severino, jika
param
(dalam fungsi di bawah) berubah segera setelah pemanggilan fungsi, Anda mungkin mendapatkan beberapa perilaku yang tidak terduga diMethodWithParameter
.Solusi Saya:
Untuk menjelaskan ini, saya akhirnya menulis sesuatu yang lebih seperti baris kode berikut:
(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);
Ini memungkinkan saya untuk menggunakan parameter dengan aman secara asinkron meskipun faktanya parameter berubah sangat cepat setelah memulai tugas (yang menyebabkan masalah dengan solusi yang diposting).
Dengan menggunakan pendekatan ini,
param
(tipe nilai) mendapatkan nilainya yang diteruskan, jadi meskipun metode asinkron berjalan setelahparam
perubahan,p
akan memiliki nilai apa pun yangparam
dimiliki saat baris kode ini dijalankan.sumber
var localParam = param; await Task.Run(() => MethodWithParam(localParam));
Cukup gunakan Task.Run
var task = Task.Run(() => { //this will already share scope with rawData, no need to use a placeholder });
Atau, jika Anda ingin menggunakannya dalam metode dan menunggu tugas nanti
public Task<T> SomethingAsync<T>() { var task = Task.Run(() => { //presumably do something which takes a few ms here //this will share scope with any passed parameters in the method return default(T); }); return task; }
sumber
for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }
itu tidak akan berperilaku sama seperti jikarawData
diteruskan seperti pada contoh StartNew OP.Tidak jelas apakah masalah aslinya adalah masalah yang sama dengan yang saya alami: ingin memaksimalkan utas CPU pada komputasi di dalam loop sambil mempertahankan nilai iterator dan menjaga inline untuk menghindari melewatkan banyak variabel ke fungsi pekerja.
for (int i = 0; i < 300; i++) { Task.Run(() => { var x = ComputeStuff(datavector, i); // value of i was incorrect var y = ComputeMoreStuff(x); // ... }); }
Saya mendapatkan ini untuk bekerja dengan mengubah iterator luar dan melokalkan nilainya dengan gerbang.
for (int ii = 0; ii < 300; ii++) { System.Threading.CountdownEvent handoff = new System.Threading.CountdownEvent(1); Task.Run(() => { int i = ii; handoff.Signal(); var x = ComputeStuff(datavector, i); var y = ComputeMoreStuff(x); // ... }); handoff.Wait(); }
sumber
Ide untuk menghindari penggunaan Sinyal seperti di atas. Memompa nilai int ke dalam struct mencegah nilai-nilai itu berubah (dalam struct). Saya punya Masalah berikut: loop var saya akan berubah sebelum DoSomething (i) dipanggil (saya bertambah di akhir loop sebelum () => DoSomething (i, i i) dipanggil). Dengan struct itu tidak terjadi lagi. Bug buruk untuk ditemukan: DoSomething (i, i ) tampak hebat, tetapi tidak pernah yakin apakah itu dipanggil setiap kali dengan nilai yang berbeda untuk i (atau hanya 100 kali dengan i = 100), maka -> struct
struct Job { public int P1; public int P2; } … for (int i = 0; i < 100; i++) { var job = new Job { P1 = i, P2 = i * i}; // structs immutable... Task.Run(() => DoSomething(job)); }
sumber