कार्य (पैरामीटर) के साथ।


88

मैं एक मल्टी-टास्किंग नेटवर्क प्रोजेक्ट पर काम कर रहा हूं और मैं नया हूं Threading.Tasks। मैंने एक सरल लागू किया Task.Factory.StartNew()और मुझे आश्चर्य है कि मैं इसे कैसे कर सकता हूंTask.Run() ?

यहाँ मूल कोड है:

Task.Factory.StartNew(new Action<object>(
(x) =>
{
    // Do something with 'x'
}), rawData);

मैंने ऑब्जेक्ट ब्राउज़रSystem.Threading.Tasks.Task में देखा और मुझे एक जैसा पैरामीटर नहीं मिला । केवल वही है जो पैरामीटर लेता है और कोई प्रकार नहीं है ।Action<T>Actionvoid

वहाँ केवल 2 चीजें हैं: static Task Run(Action action)और static Task Run(Func<Task> function)दोनों के साथ पैरामीटर पोस्ट नहीं कर सकते।

हाँ, मैं जानता हूँ कि मैं इसके लिए एक सरल विस्तार विधि बना सकते हैं, लेकिन मेरा मुख्य सवाल हम एक पंक्ति पर लिख सकते हैं है के साथ Task.Run()?


यह स्पष्ट नहीं है कि आप पैरामीटर का मान क्या चाहते हैं । यह कहां से आएगा? यदि आप इसे पहले ही प्राप्त कर चुके हैं, तो इसे लंबोदर अभिव्यक्ति में कैद करें ...
जॉन स्कीट

@JonSkeet rawDataएक नेटवर्क डेटा पैकेट है जिसमें एक कंटेनर वर्ग (जैसे DataPacket) है और मैं जीसी दबाव को कम करने के लिए इस उदाहरण का फिर से उपयोग कर रहा हूं। इसलिए, यदि मैं rawDataसीधे उपयोग करता हूं, तो Taskइसे Taskहैंडल करने से पहले इसे (संभवतः) बदला जा सकता है। अब, मुझे लगता है कि मैं इसके लिए एक और byte[]उदाहरण बना सकता हूं । मुझे लगता है कि यह मेरे लिए सबसे सरल उपाय है।
एमएफटीहमार

हां, यदि आपको बाइट सरणी को क्लोन करने की आवश्यकता है, तो आप बाइट सरणी को क्लोन करते हैं। होने से Action<byte[]>वह नहीं बदलता है।
जॉन स्कीट

किसी कार्य के लिए मापदंडों को पारित करने के लिए यहां कुछ अच्छे समाधान दिए गए हैं ।
बस शैडो

जवाबों:


116
private void RunAsync()
{
    string param = "Hi";
    Task.Run(() => MethodWithParameter(param));
}

private void MethodWithParameter(string param)
{
    //Do stuff
}

संपादित करें

लोकप्रिय मांग के कारण मुझे ध्यान देना चाहिए कि Taskलॉन्चिंग कॉलिंग थ्रेड के साथ समानांतर में चलेगा। डिफ़ॉल्ट मानकर TaskSchedulerयह .NET का उपयोग करेगा ThreadPool। वैसे भी, इसका मतलब है कि आपको जो भी पैरामीटर (ओं) को पारित करने की आवश्यकता हैTask ही समय में कई थ्रेड्स द्वारा एक्सेस किए जा रहे जितने , उन्हें साझा स्थिति बनाते हैं। इसमें उन्हें कॉलिंग थ्रेड पर एक्सेस करना शामिल है।

मेरे उपरोक्त कोड में उस मामले को पूरी तरह से बनाया गया है। तार अपरिवर्तनीय हैं। इसलिए मैंने उन्हें एक उदाहरण के रूप में इस्तेमाल किया। लेकिन कहते हैं कि तुम एक का उपयोग नहीं कर रहे हैंString ...

एक समाधान का उपयोग करना है asyncऔर await। यह, डिफ़ॉल्ट रूप से, SynchronizationContextकॉलिंग थ्रेड को कैप्चर करेगा और कॉल करने के बाद शेष विधि के लिए एक निरंतरता बनाएगा awaitऔर इसे बनाए गए से संलग्न करेगा Task। यदि यह विधि WinForms GUI थ्रेड पर चल रही है, तो यह प्रकार की होगीWindowsFormsSynchronizationContext

निरंतर कब्जा किए जाने के बाद वापस चलेगा SynchronizationContext- केवल डिफ़ॉल्ट रूप से। तो आप उस थ्रेड पर वापस आ जाएंगे जिसे आपने awaitकॉल के बाद शुरू किया था । आप इसे विभिन्न तरीकों से बदल सकते हैं, विशेष रूप से उपयोग करके ConfigureAwait। संक्षेप में, उस विधि के बाकी तक जारी नहीं किया जाएगा के बादTask किसी अन्य धागे पर पूरा । लेकिन कॉलिंग थ्रेड समानांतर में चलता रहेगा, बाकी का तरीका नहीं।

शेष विधि को पूरा करने के लिए यह प्रतीक्षा वांछनीय हो सकती है या नहीं भी हो सकती है। यदि उस पद्धति में कुछ भी नहीं है, तो बाद में Taskआपके पास दिए गए मापदंडों को एक्सेस करता है जो आप उपयोग नहीं करना चाहते हैं await

या हो सकता है कि आप उन मापदंडों का उपयोग विधि में बहुत बाद में करें। awaitतुरंत कोई कारण नहीं क्योंकि आप सुरक्षित रूप से काम करना जारी रख सकें। याद रखें, आप Taskएक चर में और awaitबाद में लौटा स्टोर कर सकते हैं - यहां तक ​​कि एक ही विधि में भी। उदाहरण के लिए, एक बार जब आप किसी अन्य कार्य को करने के बाद सुरक्षित रूप से पारित मापदंडों का उपयोग करने की आवश्यकता होती है। फिर, आप पर करने की जरूरत नहींawait हैTask इसे चलाने दाईं ओर की है।

वैसे भी, यह करने के लिए पारित मापदंडों के संबंध में इस धागे को सुरक्षित बनाने का एक सरल तरीका है Task.Run:

आप पहली बार सजाने चाहिए RunAsyncसाथ async:

private async void RunAsync()

महत्वपूर्ण लेख

अधिमानतः चिह्नित विधि को शून्य नहीं लौटना चाहिए , क्योंकि लिंक किए गए दस्तावेज़ में उल्लेख है। इसका सामान्य अपवाद ईवेंट हैंडलर जैसे बटन क्लिक और ऐसे हैं। उन्हें शून्य लौटना चाहिए। अन्यथा मैं हमेशा एक वापसी का उपयोग करते समय या कोशिश करता हूं । यह काफी कुछ कारणों से अच्छा अभ्यास है।async TaskTask<TResult>async

अब आप नीचे awaitकी Taskतरह चला सकते हैं। आप awaitबिना उपयोग नहीं कर सकते 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

तो, सामान्य तौर पर, यदि आप awaitकार्य करते हैं , तो आप एक ही बार में कई थ्रेड से कुछ संशोधित करने के सभी नुकसान के साथ संभावित साझा संसाधन के रूप में मापदंडों में पारित उपचार से बच सकते हैं। इसके अलावा, बंद होने से सावधान रहें । मैं उन लोगों को गहराई से कवर नहीं करूंगा, लेकिन जुड़ा हुआ लेख इसका एक बड़ा काम करता है।

पक्षीय लेख

थोड़ा सा विषय, लेकिन WinForms GUI थ्रेड पर किसी भी प्रकार के "ब्लॉकिंग" का उपयोग करने से सावधान रहें क्योंकि इसे चिह्नित किया जा रहा है [STAThread]। उपयोग awaitकरना बिल्कुल भी ब्लॉक नहीं होगा, लेकिन मैं कभी-कभी इसे किसी प्रकार के अवरोधन के साथ संयोजन के रूप में उपयोग करते हुए देखता हूं।

"ब्लॉक" उद्धरण में है क्योंकि आप तकनीकी रूप से WinForms GUI थ्रेड को ब्लॉक नहीं कर सकते हैं । हां, अगर आप lockWinForms GUI थ्रेड का उपयोग करते हैं, तो यह अभी भी संदेशों को पंप करेगा , आपके सोचने के बावजूद कि यह "अवरुद्ध" है। यह।

यह बहुत दुर्लभ मामलों में विचित्र मुद्दों का कारण बन सकता है। lockउदाहरण के लिए, एक कारण जब आप कभी भी पेंटिंग का उपयोग नहीं करना चाहते हैं । लेकिन यह एक फ्रिंज और जटिल मामला है; हालाँकि मैंने देखा है कि यह पागल मुद्दों का कारण है। इसलिए मैंने इसे संपूर्णता के लिए नोट किया।


23
आप का इंतजार नहीं कर रहे हैं Task.Run(() => MethodWithParameter(param));। जिसका अर्थ है कि यदि paramसंशोधित हो जाता है के बादTask.Run , आप पर अवांछित परिणाम प्राप्त हो सकता है MethodWithParameter
एलेक्जेंडर सेवरिनो

8
जब यह गलत है तो यह एक स्वीकृत जवाब क्यों है। यह पास होने वाली वस्तु के बराबर नहीं है।
ईगोर पावलखिन

7
@ Zer0 टास्क में एक दूसरा ऑब्जेक्ट ऑब्जेक्ट है। Fact.StartNew msdn.microsoft.com/en-us/library/dd321456(v=vs.110).aspx और यह इस समय ऑब्जेक्ट के मूल्य को बचाता है StartNew पर कॉल करें, जबकि आपका उत्तर एक क्लोजर बनाता है, जो संदर्भ रखता है (यदि कार्य चलाने से पहले परम का मूल्य बदल जाता है तो यह कार्य में भी बदल जाएगा), इसलिए आपका कोड बिल्कुल भी नहीं है जो सवाल पूछ रहा था । जवाब वास्तव में यह है कि टास्क के साथ इसे लिखने का कोई तरीका नहीं है। ()।
ईगोर पावलखिन

3
@ Zer0 शायद आपको सोर्स कोड पढ़ना चाहिए। एक राज्य वस्तु को पास करता है, दूसरा नहीं करता है। जो मैंने भीख से कहा है। Task.Run Task.Factory.StartNew के लिए एक छोटा हाथ नहीं है। विरासत के कारणों के लिए राज्य वस्तु संस्करण है, लेकिन यह अभी भी है और यह कभी-कभी अलग तरह से व्यवहार करता है, इसलिए लोगों को इसके बारे में पता होना चाहिए।
ईगोर पावलखिन

3
Toub का लेख पढ़ना मैं इस वाक्य पर प्रकाश डालूंगा "आपको ऑब्जेक्ट स्थिति को स्वीकार करने वाले ओवरलोड का उपयोग करना है, जो प्रदर्शन-संवेदनशील कोड पथों के लिए बंद और संबंधित आवंटन से बचने के लिए उपयोग किया जा सकता है"। मुझे लगता है कि यह तब है जब @ स्टार्टअप का उपयोग करने पर टास्क पर विचार किया जा रहा है।
21

34

"पास" मापदंडों में चर कैप्चर का उपयोग करें।

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

आप rawDataसीधे भी उपयोग कर सकते हैं, लेकिन आपको सावधान रहना चाहिए, यदि आप rawDataकिसी कार्य के बाहर के मूल्य को बदलते हैं (उदाहरण के लिए एक forलूप में एक पुनरावृत्ति) तो यह कार्य के अंदर के मूल्य को भी बदल देगा।


11
+1 महत्वपूर्ण तथ्य को ध्यान में रखते हुए कि कॉल करने के बाद चर को बदला जा सकता है Task.Run
एलेक्जेंडर सेरिनो

1
यह कैसे मदद करने वाला है? यदि आप कार्य थ्रेड के अंदर x का उपयोग करते हैं, और x किसी ऑब्जेक्ट का संदर्भ है, और यदि ऑब्जेक्ट उसी समय में संशोधित किया गया है जब कार्य थ्रेड चल रहा है तो यह कहर पैदा कर सकता है।
Ovi

1
@ Ovi-WanKenobi हां, लेकिन यह वह नहीं है जो यह सवाल था। यह कैसे एक पैरामीटर पारित करने के लिए किया गया था। यदि आप एक सामान्य फ़ंक्शन के पैरामीटर के रूप में किसी ऑब्जेक्ट के संदर्भ में पास हुए हैं, तो आपको वहां भी यही समस्या होगी।
स्कॉट चेम्बरलेन

हाँ, यह काम नहीं करता है। मेरा कार्य कॉलिंग थ्रेड में x पर वापस संदर्भ नहीं है। मैं अभी शून्य हो गया हूं।
डेविड प्राइस

स्कॉट चैंबरलेन, कैप्चर के माध्यम से पासिंग तर्क अपने स्वयं के मुद्दों के साथ आता है। विशेष रूप से, मेमोरी लीक और मेमोरी दबाव का मुद्दा है। विशेष रूप से जब आप स्केल करने की कोशिश करते हैं। (अधिक विवरण के लिए "8 तरीके आप कारण स्मृति अवकाश" देख सकते हैं)।
राशादिवर

7

अब से आप भी कर सकते हैं:

Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)

यह सबसे अच्छा उत्तर है क्योंकि यह एक राज्य को पारित करने की अनुमति देता है, और कडेन बर्गार्ट के उत्तर में उल्लिखित संभावित स्थिति को रोकता है । उदाहरण के लिए, यदि आपको IDisposableReSharper चेतावनी को हल करने के लिए कार्य प्रतिनिधि में एक ऑब्जेक्ट पास करने की आवश्यकता है "कैप्चर किया गया चर बाहरी दायरे में निपटाया जाता है" , तो यह बहुत अच्छी तरह से करता है। आम धारणा के विपरीत, जहां आपको राज्य पारित करने की आवश्यकता है, उसके Task.Factory.StartNewबजाय उपयोग करने में कुछ भी गलत नहीं है Task.Run। देखें यहाँ
नियो

7

मुझे पता है कि यह एक पुराना धागा है, लेकिन मैं एक समाधान साझा करना चाहता था जिसे मैंने स्वीकार कर लिया था क्योंकि स्वीकृत पोस्ट का अभी भी एक मुद्दा है।

समस्या:

जैसा कि अलेक्जेंड्रे सेवेरिनो द्वारा बताया गया है, अगर param(नीचे फ़ंक्शन में) फ़ंक्शन कॉल के तुरंत बाद बदलता है, तो आपको कुछ अप्रत्याशित व्यवहार मिल सकता है MethodWithParameter

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

मेरा समाधान:

इस पर ध्यान देने के लिए, मैंने कोड की निम्नलिखित पंक्ति की तरह कुछ और लिखना समाप्त कर दिया:

(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);

इसने मुझे इस तथ्य के बावजूद पैरामीटर को सुरक्षित रूप से अतुल्यकालिक रूप से उपयोग करने की अनुमति दी कि कार्य शुरू करने के बाद पैरामीटर बहुत जल्दी बदल गया (जो पोस्ट किए गए समाधान के साथ समस्या का कारण बना)।

इस दृष्टिकोण का उपयोग करते हुए, param(मूल्य प्रकार) इसके मूल्य में पारित हो जाता है, इसलिए भले ही async विधि paramपरिवर्तनों के बाद चलती है , कोड की यह रेखा चलने pपर जो कुछ भी मूल्य paramथा।


5
मैं बेसब्री से किसी का इंतजार करता हूं जो कम ओवरहेड के साथ इसे और अधिक कानूनी रूप से करने का तरीका सोच सकता है। यह स्वाभाविक रूप से बदसूरत है।
कडेन बर्गार्ट

5
यहाँ आप जाएँ:var localParam = param; await Task.Run(() => MethodWithParam(localParam));
स्टीफन क्लीयर

1
जो, वैसे, स्टीफन ने अपने जवाब में, डेढ़ साल पहले ही चर्चा की थी।
सेवित

1
@ सरवी: वह स्कॉट का जवाब था , वास्तव में। मैंने इसका उत्तर नहीं दिया।
स्टीफन क्लीयर

स्कॉट का जवाब मेरे लिए वास्तव में काम नहीं करता था, क्योंकि मैं इसे एक लूप में चला रहा था। स्थानीय यात्रा अगले पुनरावृत्ति में रीसेट हो जाती। मेरे द्वारा पोस्ट किए गए उत्तर में अंतर यह है कि परम लैम्बडा अभिव्यक्ति के दायरे में कॉपी हो जाता है, इसलिए चर तुरंत सुरक्षित है। स्कॉट के जवाब में, पैरामीटर अभी भी एक ही दायरे में है, इसलिए यह अभी भी लाइन को कॉल करने, और async फ़ंक्शन को निष्पादित करने के बीच बदल सकता है।
कडेन बर्गर्ट

5

बस Task.Run का उपयोग करें

var task = Task.Run(() =>
{
    //this will already share scope with rawData, no need to use a placeholder
});

या, यदि आप इसे एक विधि में उपयोग करना चाहते हैं और बाद में कार्य का इंतजार कर रहे हैं

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;
}

1
बस बंद करने से सावधान रहें यदि आप ऐसा करते हैं तो उस तरह for(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }से व्यवहार नहीं करेंगे rawDataजैसे कि ओपी के स्टार्टअप उदाहरण में पारित किया गया था।
स्कॉट चैंबरलेन

@ScottChamberlain - यह एक अलग उदाहरण की तरह लगता है;) मुझे आशा है कि ज्यादातर लोग लैम्ब्डा मूल्यों पर बंद होने के बारे में समझेंगे।
ट्रैविस जे

3
और यदि उन पिछली टिप्पणियों से कोई मतलब नहीं था, तो कृपया एरिक लिपर के ब्लॉग को इस विषय पर देखें: blogs.msdn.com/b/ericlippert/archive/2009/11/12/…। यह बताता है कि ऐसा क्यों होता है।
ट्रैविस जे

2

यह स्पष्ट नहीं है कि अगर मूल समस्या वही समस्या थी जो मेरे पास थी: एक लूप के अंदर अभिकलन पर अधिकतम सीपीयू थ्रेड्स को चाहने के दौरान, एटरेटर के मान को संरक्षित करते हुए और इनलाइन को वर्कर टन के एक वर्कर फंक्शन से बचने के लिए इनलाइन रखना।

for (int i = 0; i < 300; i++)
{
    Task.Run(() => {
        var x = ComputeStuff(datavector, i); // value of i was incorrect
        var y = ComputeMoreStuff(x);
        // ...
    });
}

मुझे बाहरी इटर्चर को बदलने और एक गेट के साथ इसके मूल्य को स्थानीयकृत करने के लिए काम करने के लिए मिला।

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();
}

0

आइडिया ऊपर दिए सिग्नल की तरह इस्तेमाल करने से बचना है। एक संरचना में अंतर मूल्यों को पंप करना उन मूल्यों को बदलने से रोकता है (संरचना में)। मुझे निम्न समस्या थी: DoSomething (i) कहा जाता था से पहले लूप var i बदल जाएगा (i) लूप के अंत में वेतन वृद्धि हुई थी () => DoSomething (i, i i) कहा जाता था)। संरचनाओं के साथ यह अब नहीं होता है। खोजने के लिए गंदा बग: DoSomething (i, i i) बहुत अच्छा लग रहा है, लेकिन यह सुनिश्चित करने के लिए कि क्या यह हर बार i के लिए एक अलग मान के साथ नहीं मिलता है (या i = 100 के साथ सिर्फ 100 बार), इसलिए -> संरचना

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));
}

1
हालांकि यह प्रश्न का उत्तर दे सकता है, लेकिन इसे समीक्षा के लिए ध्वजांकित किया गया था। बिना किसी स्पष्टीकरण के उत्तर को अक्सर निम्न-गुणवत्ता माना जाता है। कृपया सही उत्तर क्यों है, इसके लिए कुछ टिप्पणी दें।
डैन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.