Task.Run dengan Parameter (s)?

88

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.Taskdalam Browser Objek dan saya tidak dapat menemukan Action<T>parameter sejenis. Hanya ada Actionyang mengambil voidparameter 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()?

MFatihMAR
sumber
Tidak jelas apa yang Anda inginkan dari nilai parameter. Dari mana asalnya? Jika Anda sudah mendapatkannya, tangkap saja dalam ekspresi lambda ...
Jon Skeet
@JonSkeet rawDataadalah paket data jaringan yang memiliki kelas kontainer (seperti DataPacket) dan saya menggunakan kembali contoh ini untuk mengurangi tekanan GC. Jadi, jika saya menggunakan rawDatasecara langsung Task, itu (mungkin) dapat diubah sebelum Taskmenanganinya. Sekarang, saya rasa saya bisa membuat byte[]contoh lain untuk itu. Saya pikir ini solusi paling sederhana untuk saya.
MFatihMAR
Ya, jika Anda perlu mengkloning byte array, Anda mengkloning byte array. Memiliki sebuah Action<byte[]>tidak mengubah itu.
Jon Skeet
Berikut beberapa solusi bagus untuk meneruskan parameter ke tugas.
Just Shadow

Jawaban:

116
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 Taskpeluncuran akan berjalan secara paralel dengan utas panggilan. Dengan asumsi default TaskSchedulerini akan menggunakan .NET ThreadPool. Bagaimanapun, ini berarti Anda perlu memperhitungkan parameter apa pun yang diteruskan ke yang Taskberpotensi 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 asyncdan await. Ini, secara default, akan menangkap SynchronizationContextthread pemanggil dan akan membuat kelanjutan untuk metode lainnya setelah panggilan ke awaitdan melampirkannya ke yang dibuat Task. Jika metode ini berjalan di utas WinForms GUI, itu akan menjadi tipe WindowsFormsSynchronizationContext.

Kelanjutan akan berjalan setelah diposting kembali ke yang ditangkap SynchronizationContext- lagi hanya secara default. Jadi Anda akan kembali ke utas yang Anda mulai setelah awaitpanggilan. Anda dapat mengubahnya dengan berbagai cara, terutama dengan menggunakan ConfigureAwait. Singkatnya, sisa metode yang tidak akan berlanjut sampai setelah yang Tasktelah 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 TaskAnda mungkin tidak ingin menggunakan awaitsama sekali.

Atau mungkin Anda menggunakan parameter tersebut nanti dalam metode. Tidak ada alasan untuk awaitsegera karena Anda bisa terus melakukan pekerjaan dengan aman. Ingat, Anda bisa menyimpan yang Taskdikembalikan dalam variabel dan awaitnanti - bahkan dalam metode yang sama. Misalnya, setelah Anda perlu mengakses parameter yang diteruskan dengan aman setelah melakukan banyak pekerjaan lain. Sekali lagi, Anda tidak perlu awaitdi Taskkanan saat Anda menjalankannya.

Bagaimanapun, cara sederhana untuk membuat thread ini aman sehubungan dengan parameter yang diteruskan Task.Runadalah dengan melakukan ini:

Anda harus terlebih dahulu mendekorasi RunAsyncdengan async:

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 TaskTask<TResult>async

Sekarang Anda dapat awaitmenjalankan Taskseperti di bawah ini. Anda tidak dapat menggunakan awaittanpa async.

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 awaitmelakukan 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]. Penggunaan awaittidak 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 lockpada 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 locksaat melukis, misalnya. Tapi itu kasus pinggiran dan kompleks; Namun saya telah melihatnya menyebabkan masalah gila. Jadi saya mencatatnya demi kelengkapan.

Zer0
sumber
23
Anda tidak menunggu Task.Run(() => MethodWithParameter(param));. Berarti bahwa jika yang paramakan diubah setelah itu Task.Run, Anda mungkin memiliki hasil yang tidak diharapkan pada MethodWithParameter.
Alexandre Severino
8
Mengapa ini jawaban yang diterima padahal salah. Itu sama sekali tidak setara dengan objek keadaan yang lewat.
Egor Pavlikhin
7
@ Zer0 objek status adalah paremeter kedua di Task.Factory.StartNew msdn.microsoft.com/en-us/library/dd321456(v=vs.110).aspx dan itu menyimpan nilai objek pada saat panggilan ke StartNew, sementara jawaban Anda membuat penutupan, yang menyimpan referensi (jika nilai param berubah sebelum tugas dijalankan, itu juga akan berubah dalam tugas), jadi kode Anda sama sekali tidak setara dengan pertanyaan yang diajukan . Jawabannya sebenarnya adalah tidak ada cara untuk menulisnya dengan Task.Run ().
Egor Pavlikhin
3
@ Zer0 Mungkin Anda harus membaca kode sumber. Satu melewati objek negara, yang lainnya tidak. Itulah yang saya katakan dari awal. Task.Run bukanlah kependekan dari Task.Factory.StartNew. Versi objek status ada karena alasan lama, tetapi masih ada dan terkadang berperilaku berbeda, jadi orang harus menyadarinya.
Egor Pavlikhin
3
Membaca artikel Toub, saya akan menyoroti kalimat ini "Anda bisa menggunakan kelebihan beban yang menerima status objek, yang untuk jalur kode yang sensitif terhadap kinerja dapat digunakan untuk menghindari penutupan dan alokasi yang sesuai". Saya pikir inilah yang disiratkan @Zero ketika mempertimbangkan Task.Run over StartNew use.
davidcarr
34

Gunakan tangkapan variabel untuk "meneruskan" parameter.

var x = rawData;
Task.Run(() =>
{
    // Do something with 'x'
});

Anda juga dapat menggunakan rawDatasecara langsung tetapi Anda harus berhati-hati, jika Anda mengubah nilai di rawDataluar tugas (misalnya iterator dalam satu forlingkaran) itu juga akan mengubah nilai di dalam tugas.

Scott Chamberlain
sumber
11
1 untuk mempertimbangkan fakta penting bahwa variabel dapat diubah segera setelah dipanggil Task.Run.
Alexandre Severino
1
bagaimana ini bisa membantu? jika Anda menggunakan x di dalam utas tugas, dan x adalah referensi ke sebuah objek, dan jika objek diubah dalam waktu yang sama saat utas tugas sedang berjalan, hal itu dapat menyebabkan malapetaka.
Ovi
1
@ Ovi-WanKenobi Ya, tapi pertanyaan ini bukanlah tentang itu. Itu adalah cara melewatkan parameter. Jika Anda mengirimkan referensi ke objek sebagai parameter ke fungsi normal, Anda juga akan memiliki masalah yang sama persis di sana.
Scott Chamberlain
Yup ini tidak berhasil. Tugas saya tidak memiliki referensi kembali ke x di utas panggilan. Saya baru saja mendapatkan nol.
David Price
Scott Chamberlain, Meneruskan argumen melalui penangkapan memiliki masalah tersendiri. Secara khusus, ada masalah kebocoran memori dan tekanan memori. Khususnya saat Anda mencoba untuk meningkatkan. (lihat "8 Cara Anda Dapat Menyebabkan Kebocoran Memori" untuk lebih detil).
RashadRivera
7

Mulai sekarang Anda juga dapat:

Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)
Arnaud F.
sumber
Ini adalah jawaban terbaik karena memungkinkan sebuah negara diteruskan, dan mencegah kemungkinan situasi yang disebutkan dalam jawaban Kaden Burgart . Misalnya, jika Anda perlu meneruskan IDisposableobjek 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 menggunakan Task.Factory.StartNewalih-alih di Task.Runmana Anda harus melewati keadaan. Lihat disini .
Neo
7

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 di MethodWithParameter.

Task.Run(() => MethodWithParameter(param)); 

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 setelah paramperubahan, pakan memiliki nilai apa pun yang paramdimiliki saat baris kode ini dijalankan.

Kaden Burgart
sumber
5
Saya sangat menantikan siapa saja yang dapat memikirkan cara untuk melakukan ini secara lebih jelas dengan biaya yang lebih sedikit. Ini memang agak jelek.
Kaden Burgart
5
Ini dia:var localParam = param; await Task.Run(() => MethodWithParam(localParam));
Stephen Cleary
1
Omong-omong, Stephen sudah membahas jawabannya, satu setengah tahun yang lalu.
Pelayanan
1
@ Servy: Sebenarnya itu adalah jawaban Scott . Saya tidak menjawab yang ini.
Stephen Cleary
Jawaban Scott sebenarnya tidak akan berhasil untuk saya, karena saya menjalankan ini secara berulang. Parameter lokal akan disetel ulang pada iterasi berikutnya. Perbedaan dalam jawaban yang saya posting adalah parameter disalin ke dalam lingkup ekspresi lambda, sehingga variabel segera aman. Dalam jawaban Scott, parameternya masih dalam lingkup yang sama, jadi masih bisa berubah antara memanggil baris, dan menjalankan fungsi async.
Kaden Burgart
5

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;
}
Travis J
sumber
1
Berhati-hatilah dengan penutupan jika Anda melakukannya dengan cara for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }itu tidak akan berperilaku sama seperti jika rawDataditeruskan seperti pada contoh StartNew OP.
Scott Chamberlain
@ScottChamberlain - Itu tampak seperti contoh yang berbeda;) Saya berharap kebanyakan orang memahami tentang menutup nilai lambda.
Travis J
3
Dan jika komentar sebelumnya tidak masuk akal, silakan lihat blog Eric Lipper dengan topik: blogs.msdn.com/b/ericlippert/archive/2009/11/12/… Ini menjelaskan mengapa ini terjadi dengan sangat baik.
Travis J
2

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();
}
Harald J.
sumber
0

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));
}
CodeDigger
sumber
1
Meskipun ini mungkin menjawab pertanyaan, itu ditandai untuk ditinjau. Jawaban tanpa penjelasan sering dianggap berkualitas rendah. Tolong berikan beberapa komentar mengapa ini adalah jawaban yang benar.
Dan