UI थ्रेड पर कार्य निरंतरता


214

क्या यह निर्दिष्ट करने का कोई 'मानक' तरीका है कि एक कार्य निरंतरता उस थ्रेड पर चलना चाहिए जिससे प्रारंभिक कार्य बनाया गया था?

वर्तमान में मेरे पास कोड नीचे है - यह काम कर रहा है लेकिन डिस्पैचर का ध्यान रखना और एक दूसरी क्रिया बनाना अनावश्यक ओवरहेड की तरह लगता है।

dispatcher = Dispatcher.CurrentDispatcher;
Task task = Task.Factory.StartNew(() =>
{
    DoLongRunningWork();
});

Task UITask= task.ContinueWith(() =>
{
    dispatcher.Invoke(new Action(() =>
    {
        this.TextBlock1.Text = "Complete"; 
    }
});

अपने उदाहरण के मामले में, आप उपयोग कर सकते हैं Control.Invoke(Action), अर्थात। TextBlock1.Invokeइसके बजायdispatcher.Invoke
कर्नल पैनिक

2
धन्यवाद @ColonelPanic, लेकिन मैं WPF (टैग के रूप में) का उपयोग कर रहा था, विनफॉर्म नहीं।
ग्रेग सैन्सोम जूल

जवाबों:


352

इसके साथ निरंतरता को कॉल करें TaskScheduler.FromCurrentSynchronizationContext():

    Task UITask= task.ContinueWith(() =>
    {
     this.TextBlock1.Text = "Complete"; 
    }, TaskScheduler.FromCurrentSynchronizationContext());

यह केवल तभी उपयुक्त है जब वर्तमान निष्पादन संदर्भ UI थ्रेड पर है।


39
यह केवल तभी मान्य है जब वर्तमान निष्पादन संदर्भ UI थ्रेड पर है। यदि आप इस कोड को किसी अन्य कार्य के अंदर रखते हैं, तो आपको InvalidOperationException ( अपवाद अनुभाग देखें)
stukselbax

3
.NET 4.5 में, जोहान लार्सन के उत्तर को यूआई थ्रेड पर कार्य निरंतरता के लिए मानक तरीके के रूप में उपयोग किया जाना चाहिए। बस लिखें: टास्क का इंतजार करें। (DoLongRunningWork); this.TextBlock1.Text = "पूरा"; इसे भी देखें: blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Marcel W

1
मेरी जान बचाने के लिए Thx। मैं प्रतीक्षा / जारी रखने के लिए मुख्य थ्रेड चीजों को कॉल करने का तरीका जानने के लिए घंटों बिताता हूं। बाकी सभी के लिए एकता के लिए Google Firebase SDK का उपयोग कैसे किया जाता है और अभी भी समान मुद्दे हैं, यह एक कार्यशील दृष्टिकोण है।
सीएचपी

2
@MarcelW - awaitएक अच्छा पैटर्न है - लेकिन केवल अगर आप एक asyncसंदर्भ के अंदर हैं (जैसे कि एक विधि घोषित async)। यदि नहीं, तो इस उत्तर की तरह कुछ करना अभी भी आवश्यक है।
टूलमेकरसेव

33

Async के साथ आप बस करते हैं:

await Task.Run(() => do some stuff);
// continue doing stuff on the same context as before.
// while it is the default it is nice to be explicit about it with:
await Task.Run(() => do some stuff).ConfigureAwait(true);

तथापि:

await Task.Run(() => do some stuff).ConfigureAwait(false);
// continue doing stuff on the same thread as the task finished on.

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

1
@ToolmakerSteve निर्भर करता है कि आप किस थ्रेड के बारे में सोच रहे हैं। वर्कर थ्रेड का उपयोग Task.Run या कॉलर थ्रेड द्वारा किया जाता है? याद रखें, "एक ही सूत्र पर समाप्त कार्य" का अर्थ है श्रमिक धागा (धागे के बीच 'स्विचिंग' से बचना)। इसके अलावा, ConfigureAwait (सच) यह गारंटी नहीं देता है कि नियंत्रण उसी थ्रेड पर वापस लौटता है , केवल उसी संदर्भ में (हालांकि अंतर महत्वपूर्ण नहीं हो सकता है)।
मैक्स बैरक्लोफ़

@MaxBarraclough - धन्यवाद, मैं गलत था जो "एक ही धागा" था। थ्रेड के बीच स्विचिंग से बचने के अर्थ में प्रदर्शन को अधिकतम करने के लिए जो कुछ भी धागा हो रहा है [[कुछ सामान "कार्य करने के लिए], जो मेरे लिए इसे स्पष्ट करता है।
टूलमेकरसेव

1
प्रश्न एक asyncविधि के अंदर होने का उल्लेख नहीं करता है (जो कि उपयोग करने के लिए आवश्यक है await)। जब awaitउपलब्ध नहीं है तो उत्तर क्या है ?
टूलमेकरसेव

22

यदि आपके पास रिटर्न वैल्यू है, तो आपको यूआई में भेजने की आवश्यकता है, आप इस तरह से जेनेरिक संस्करण का उपयोग कर सकते हैं:

यह मेरे मामले में एक MVVM ViewModel से बुलाया जा रहा है।

var updateManifest = Task<ShippingManifest>.Run(() =>
    {
        Thread.Sleep(5000);  // prove it's really working!

        // GenerateManifest calls service and returns 'ShippingManifest' object 
        return GenerateManifest();  
    })

    .ContinueWith(manifest =>
    {
        // MVVM property
        this.ShippingManifest = manifest.Result;

        // or if you are not using MVVM...
        // txtShippingManifest.Text = manifest.Result.ToString();    

        System.Diagnostics.Debug.WriteLine("UI manifest updated - " + DateTime.Now);

    }, TaskScheduler.FromCurrentSynchronizationContext());

मैं अनुमान लगा रहा हूँ = इससे पहले कि GenerateManifest एक टाइपो हो।
सेबस्टियन एफ।

हाँ - अब गया! धन्यवाद।
सिमोन_विवर

11

मैं सिर्फ इस संस्करण को जोड़ना चाहता था क्योंकि यह इतना उपयोगी धागा है और मुझे लगता है कि यह एक बहुत ही सरल कार्यान्वयन है। यदि मैंने मल्टीथ्रेड अनुप्रयोग का उपयोग कई बार किया है

 Task.Factory.StartNew(() =>
      {
        DoLongRunningWork();
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
              { txt.Text = "Complete"; }));
      });

2
कुछ परिदृश्यों में यह व्यवहार्य समाधान नहीं है; हालाँकि, स्वीकृत उत्तर बेहतर है। यह प्रौद्योगिकी-अज्ञेयवादी है ( TaskSchedulerबीसीएल का हिस्सा है, Dispatcherनहीं है) और किसी भी अग्नि-विस्मरण के कारण होने वाली असिंच संचालन (जैसे कि BeginInvoke) के बारे में चिंता न करने के कारण कार्यों की जटिल श्रृंखलाओं की रचना करने के लिए इस्तेमाल किया जा सकता है ।
किरिल श्लेन्स्की

@Kirill आप थोड़ा विस्तार कर सकते हैं, क्योंकि कुछ SO थ्रेड्स ने सर्वसम्मति से WinForms के WPF का उपयोग करते हुए डिस्पैचर को सही विधि घोषित कर दिया है: एक GUI अपडेट को या तो एसिंक्रोनस रूप से शुरू कर सकता है (शुरुआत में) उपयोग किया जाता है क्योंकि कोई एक GUI अद्यतन के लिए पृष्ठभूमि थ्रेड को ब्लॉक नहीं करना चाहेगा। क्या FromCurrentSynchronizationContext प्रेषण कार्य को मुख्य थ्रेड संदेश कतार में डिस्पैचर की तरह ही नहीं रखता है?
डीन

1
ठीक है, लेकिन ओपी निश्चित रूप से WPF के बारे में पूछ रहा है (और इसे टैग किया गया है), और किसी भी डिस्पैचर का संदर्भ नहीं रखना चाहता है (और मैं किसी भी तुल्यकालन संदर्भ को मानता हूं - आप इसे केवल मुख्य धागे से प्राप्त कर सकते हैं और आपके पास है इसे कहीं पर स्टोर करें)। यही कारण है कि मुझे जो समाधान पोस्ट किया गया है वह मुझे पसंद है: इसमें बनाया गया एक थ्रेड-सुरक्षित स्थैतिक संदर्भ है, इसके लिए किसी की आवश्यकता नहीं है। मुझे लगता है कि यह WPF के संदर्भ में बेहद उपयोगी है।
डीन

3
बस मेरी पिछली टिप्पणी को सुदृढ़ करना चाहता था: डेवलपर को न केवल सिंक संदर्भ को संग्रहीत करना होगा, बल्कि उसे यह जानना होगा कि यह केवल मुख्य धागे से उपलब्ध है; यह समस्या एसओ के दर्जनों प्रश्नों में भ्रम का कारण रही है: लोग हर समय कार्यकर्ता सूत्र से इसे प्राप्त करने का प्रयास करते हैं। यदि उनके कोड को स्वयं एक श्रमिक थ्रेड में ले जाया गया है, तो यह इस समस्या के कारण विफल हो जाता है। इसलिए डब्ल्यूपीएफ की व्यापकता के कारण, यह निश्चित रूप से यहाँ इस लोकप्रिय प्रश्न में स्पष्ट किया जाना चाहिए।
डीन

1
... फिर भी, डीन का अवलोकन [स्वीकृत उत्तर] के बारे में है जिसे सिंक संदर्भ का ट्रैक रखने की आवश्यकता है यदि कोड मुख्य धागे पर नहीं हो सकता है, यह नोट करना महत्वपूर्ण है, और इससे बचने के लिए इस उत्तर का एक लाभ है।
टूलमेकरसेव

1

Google के माध्यम से यहाँ मिल गया क्योंकि मैं एक अच्छा तरीका की तलाश कर रहा था जो कि Task un के अंदर होने के बाद ui थ्रेड पर काम करता है - निम्नलिखित कोड का उपयोग awaitकरके आप फिर से UI थ्रेड पर वापस आ सकते हैं ।

मुझे उम्मीद है इससे किसी को सहायता मिलेगी।

public static class UI
{
    public static DispatcherAwaiter Thread => new DispatcherAwaiter();
}

public struct DispatcherAwaiter : INotifyCompletion
{
    public bool IsCompleted => Application.Current.Dispatcher.CheckAccess();

    public void OnCompleted(Action continuation) => Application.Current.Dispatcher.Invoke(continuation);

    public void GetResult() { }

    public DispatcherAwaiter GetAwaiter()
    {
        return this;
    }
}

उपयोग:

... code which is executed on the background thread...
await UI.Thread;
... code which will be run in the application dispatcher (ui thread) ...

बहुत चालाक! हालांकि unintuitive काफी है। मैं staticकक्षा बनाने का सुझाव देता हूं UI
थियोडोर जूलियास
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.