मैं GUI को किसी अन्य थ्रेड से कैसे अपडेट करूं?


1392

Labelदूसरे से अपडेट करने का सबसे सरल तरीका कौन सा है Thread?

  • मेरे पास एक Formचालू है thread1, और उससे मैं एक और धागा शुरू कर रहा हूं ( thread2)।

  • thread2कुछ फाइलों को संसाधित करते समय मैं वर्तमान स्थिति के काम Labelके Formसाथ अपडेट करना चाहूंगा thread2

ऐसा कैसे किया जा सकता था?


25
.Net 2.0 के लिए सिर्फ इस के लिए BackgroundWorker वर्ग नहीं है। यह यूआई धागा पता है। 1. एक बैकग्राउंड बनाएं। 2. दो डेलीगेट्स (प्रोसेसिंग के लिए एक, और एक पूरा करने के लिए) जोड़ें
प्रीत संघ

13
शायद थोड़ी देर: codeproject.com/KB/cs/Threadsafe_formupdating.aspx
MichaelD


5
यह प्रश्न Gtk # GUI पर लागू नहीं होता है। Gtk के लिए # यह और यह उत्तर देखें।
हवलदार

खबरदार: इस सवाल के जवाब अब ओटी ("यहां मैंने अपने WPF ऐप के लिए क्या किया है") और ऐतिहासिक .NET 2.0 कलाकृतियों की गड़बड़ है।
मार्क एल।

जवाबों:


768

.NET 2.0 के लिए, यहाँ एक अच्छा सा कोड मैंने लिखा है जो वास्तव में आपको चाहिए, और किसी भी संपत्ति के लिए काम करता है Control:

private delegate void SetControlPropertyThreadSafeDelegate(
    Control control, 
    string propertyName, 
    object propertyValue);

public static void SetControlPropertyThreadSafe(
    Control control, 
    string propertyName, 
    object propertyValue)
{
  if (control.InvokeRequired)
  {
    control.Invoke(new SetControlPropertyThreadSafeDelegate               
    (SetControlPropertyThreadSafe), 
    new object[] { control, propertyName, propertyValue });
  }
  else
  {
    control.GetType().InvokeMember(
        propertyName, 
        BindingFlags.SetProperty, 
        null, 
        control, 
        new object[] { propertyValue });
  }
}

इसे इस तरह से कॉल करें:

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);

यदि आप .NET 3.0 या इसके बाद के संस्करण का उपयोग कर रहे हैं, तो आप उपरोक्त विधि को Controlवर्ग के विस्तार विधि के रूप में फिर से लिख सकते हैं , जो तब कॉल को सरल करेगा:

myLabel.SetPropertyThreadSafe("Text", status);

अद्यतन 05/10/2010:

.NET 3.0 के लिए आपको इस कोड का उपयोग करना चाहिए:

private delegate void SetPropertyThreadSafeDelegate<TResult>(
    Control @this, 
    Expression<Func<TResult>> property, 
    TResult value);

public static void SetPropertyThreadSafe<TResult>(
    this Control @this, 
    Expression<Func<TResult>> property, 
    TResult value)
{
  var propertyInfo = (property.Body as MemberExpression).Member 
      as PropertyInfo;

  if (propertyInfo == null ||
      !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
      @this.GetType().GetProperty(
          propertyInfo.Name, 
          propertyInfo.PropertyType) == null)
  {
    throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
  }

  if (@this.InvokeRequired)
  {
      @this.Invoke(new SetPropertyThreadSafeDelegate<TResult> 
      (SetPropertyThreadSafe), 
      new object[] { @this, property, value });
  }
  else
  {
      @this.GetType().InvokeMember(
          propertyInfo.Name, 
          BindingFlags.SetProperty, 
          null, 
          @this, 
          new object[] { value });
  }
}

जो कि अधिक क्लीनर, सरल और सुरक्षित सिंटैक्स की अनुमति देने के लिए LINQ और लैम्ब्डा अभिव्यक्ति का उपयोग करता है:

myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile

न केवल संपत्ति का नाम अब संकलित समय पर जांचा गया है, संपत्ति का प्रकार भी ठीक है, इसलिए यह असंभव है (उदाहरण के लिए) एक बूलियन संपत्ति के लिए एक स्ट्रिंग मान असाइन करें, और इसलिए एक रनटाइम अपवाद का कारण है।

दुर्भाग्य से यह किसी को बेवकूफी करने से नहीं रोकता है जैसे कि किसी अन्य Controlकी संपत्ति और मूल्य में गुजरना , इसलिए निम्नलिखित खुशी से संकलन करेगा:

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

इसलिए मैंने रनटाइम चेक को यह सुनिश्चित करने के लिए जोड़ा कि पारित संपत्ति वास्तव में Controlउस विधि से संबंधित है जिस पर कॉल किया जा रहा है। सही नहीं है, लेकिन अभी भी .NET 2.0 संस्करण की तुलना में बहुत बेहतर है।

यदि किसी के पास कोई सुझाव है कि संकलन-समय सुरक्षा के लिए इस कोड को कैसे बेहतर बनाया जाए, तो कृपया टिप्पणी करें!


3
ऐसे मामले हैं जब यह.GetType () propertyInfo.ReflectedType (जैसे WinForms पर LinkLabel) के समान मूल्यांकन करता है। मेरे पास एक बड़ा C # अनुभव नहीं है, लेकिन मुझे लगता है कि अपवाद की स्थिति होनी चाहिए: if (propertyInfo ==ull || (!@this.GetType) ()। IsSubclassOf (propertyInfo.RefanedType) && @ this.GetType () )! = propertyInfo.ReflectedType) || @ this.GetType ()। GetProperty (propertyInfo.Name, propertyInfo.PropertyType) == null)
Corvin

9
@ इसे SetControlPropertyThreadSafe(myLabel, "Text", status)दूसरे मॉड्यूल या क्लास या फॉर्म से बुलाया जा सकता है
स्मिथ

71
प्रदान किया गया समाधान अनावश्यक रूप से जटिल है। यदि आप सादगी को महत्व देते हैं, तो मार्क ग्रेवेल का समाधान, या ज़ैद मसूद का समाधान देखें।
फ्रैंक हिलमैन

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

4
पृथ्वी पर आप इस कोड का उपयोग बैकग्राउंडर घटक पर क्यों करेंगे?
एंडी

1079

सबसे सरल तरीका एक अनाम विधि है जिसमें पारित किया गया है Label.Invoke:

// Running on the worker thread
string newText = "abc";
form.Label.Invoke((MethodInvoker)delegate {
    // Running on the UI thread
    form.Label.Text = newText;
});
// Back on the worker thread

Invokeजब तक यह पूरा न हो जाए, तब तक सूचना को रोकें - यह समकालिक कोड है। सवाल अतुल्यकालिक कोड के बारे में नहीं पूछता है, लेकिन जब आप इसके बारे में सीखना चाहते हैं तो अतुल्यकालिक कोड लिखने के बारे में स्टैक ओवरफ्लो पर बहुत सारी सामग्री है।


8
देखकर रूप में ओपी किसी भी वर्ग / उदाहरण का उल्लेख नहीं किया गया है को छोड़कर रूप है, कि एक बुरा डिफ़ॉल्ट नहीं है ...
मार्क Gravell

39
"यह" कीवर्ड मत भूलना एक "नियंत्रण" वर्ग को संदर्भित कर रहा है।
अज।

8
@codecompleting यह किसी भी तरह से सुरक्षित है, और हम पहले से ही जानते हैं कि हम एक कार्यकर्ता पर हैं, इसलिए हम जो कुछ जानते हैं, उसकी जांच क्यों करें?
मार्क Gravell

4
@Dragouf वास्तव में नहीं है - इस पद्धति का उपयोग करने के बिंदु में से एक यह है कि आप पहले से ही जानते हैं कि कौन से भाग कार्यकर्ता पर चलते हैं, और जो यूआई थ्रेड पर चलते हैं। जांच की जरूरत नहीं।
मार्क Gravell

3
@ Joan.bdm इस पर टिप्पणी करने के लिए मेरे लिए कहीं नहीं पर्याप्त संदर्भ के पास है
मार्क Gravell

399

लंबे काम को संभालना

चूंकि .NET 4.5 और सी # 5.0 का उपयोग करना चाहिए टास्क आधारित अतुल्यकालिक पैटर्न (TAP) के साथ-साथ async - इंतजार कीवर्ड सभी क्षेत्रों में (जीयूआई सहित):

TAP नए विकास के लिए अनुशंसित अतुल्यकालिक डिजाइन पैटर्न है

अतुल्यकालिक प्रोग्रामिंग मॉडल (APM) और इवेंट-आधारित अतुल्यकालिक पैटर्न (EAP) के बजाय (बाद में बैकग्राउंडवॉकर क्लास शामिल है )।

फिर, नए विकास के लिए अनुशंसित समाधान है:

  1. एक घटना हैंडलर के अतुल्यकालिक कार्यान्वयन (हाँ, यह सब है):

    private async void Button_Clicked(object sender, EventArgs e)
    {
        var progress = new Progress<string>(s => label.Text = s);
        await Task.Factory.StartNew(() => SecondThreadConcern.LongWork(progress),
                                    TaskCreationOptions.LongRunning);
        label.Text = "completed";
    }
  2. यूआई थ्रेड को सूचित करने वाले दूसरे धागे का कार्यान्वयन:

    class SecondThreadConcern
    {
        public static void LongWork(IProgress<string> progress)
        {
            // Perform a long running work...
            for (var i = 0; i < 10; i++)
            {
                Task.Delay(500).Wait();
                progress.Report(i.ToString());
            }
        }
    }

निम्नलिखित पर ध्यान दें:

  1. कॉलबैक और स्पष्ट धागे के बिना अनुक्रमिक तरीके से लिखे गए लघु और स्वच्छ कोड।
  2. थ्रेड के बजाय टास्क
  3. async कीवर्ड, जो प्रतीक्षा का उपयोग करने की अनुमति देता है, जो बदले में ईवेंट हैंडलर को कार्य समाप्त होने तक पूर्ण स्थिति तक पहुंचने से रोकता है और इस बीच UI थ्रेड को ब्लॉक नहीं करता है।
  4. प्रगति वर्ग ( IProgress Interface देखें ) जो सेपरेशन ऑफ़ कंसर्न (SoC) डिज़ाइन सिद्धांत का समर्थन करता है और इसके लिए स्पष्ट डिस्पैचर और इनवोकिंग की आवश्यकता नहीं होती है। यह अपने निर्माण स्थान (यहां UI थ्रेड) से वर्तमान सिंक्रोनाइज़ेशन कॉनटेक्स्ट का उपयोग करता है ।
  5. TaskCreationOptions.LongRunning कि संकेत में कार्य कतार नहीं ThreadPool

अधिक क्रियात्मक उदाहरणों के लिए देखें: द फ्यूचर ऑफ सी #: जोसफ अलबहारी द्वारा 'प्रतीक्षा' करने वालों के लिए अच्छी चीजें आती हैं

यूआई थ्रेडिंग मॉडल अवधारणा के बारे में भी देखें ।

अपवादों को संभालना

नीचे का स्निपेट एक उदाहरण है Enabledकि पृष्ठभूमि के निष्पादन के दौरान कई क्लिकों को रोकने के लिए अपवादों को कैसे प्रबंधित किया जाए और बटन को कैसे टॉगल किया जाए ।

private async void Button_Click(object sender, EventArgs e)
{
    button.Enabled = false;

    try
    {
        var progress = new Progress<string>(s => button.Text = s);
        await Task.Run(() => SecondThreadConcern.FailingWork(progress));
        button.Text = "Completed";
    }
    catch(Exception exception)
    {
        button.Text = "Failed: " + exception.Message;
    }

    button.Enabled = true;
}

class SecondThreadConcern
{
    public static void FailingWork(IProgress<string> progress)
    {
        progress.Report("I will fail in...");
        Task.Delay(500).Wait();

        for (var i = 0; i < 3; i++)
        {
            progress.Report((3 - i).ToString());
            Task.Delay(500).Wait();
        }

        throw new Exception("Oops...");
    }
}

2
यदि SecondThreadConcern.LongWork()कोई अपवाद फेंकता है, तो क्या यह UI थ्रेड द्वारा पकड़ा जा सकता है? यह एक उत्कृष्ट पोस्ट है, btw।
kdbanman

2
मैंने आपकी आवश्यकताओं को पूरा करने के लिए उत्तर में एक अतिरिक्त अनुभाग जोड़ा है। सादर।
रिसजार्ड डीगन

3
ExceptionDispatchInfo वर्ग async-इंतजार पैटर्न में यूआई धागे पर पृष्ठभूमि अपवाद rethrowing की कि चमत्कार के लिए जिम्मेदार है।
रेज़्ज़र्ड डिएगन

1
क्या यह सिर्फ मुझे सोचने में है कि ऐसा करने का यह तरीका सिर्फ आह्वान करने / शुरू करने की तुलना में अधिक क्रिया है?
मीटिटस

2
Task.Delay(500).Wait()? वर्तमान थ्रेड को ब्लॉक करने के लिए टास्क बनाने की क्या बात है? आपको थ्रेड पूल थ्रेड को कभी भी ब्लॉक नहीं करना चाहिए!
यरिक

236

.NET 4 के लिए मार्क ग्रेवेल के सबसे सरल समाधान का रूपांतर :

control.Invoke((MethodInvoker) (() => control.Text = "new text"));

या इसके बजाय कार्रवाई प्रतिनिधि का उपयोग करें:

control.Invoke(new Action(() => control.Text = "new text"));

दो की तुलना के लिए यहां देखें: MethodInvoker vs Action for Control.BeginInvoke


1
इस उदाहरण में 'नियंत्रण' क्या है? मेरा UI नियंत्रण? WPF को एक लेबल नियंत्रण पर लागू करने की कोशिश कर रहा है, और Invoke मेरे लेबल का सदस्य नहीं है।
डब्लूओएम

विस्तार विधि के बारे में क्या है @styxriver stackoverflow.com/a/3588137/206730 ?
बजे कीकनेट

'एक्शन वाई' घोषित करें पाठ संपत्ति को बदलने वाले वर्ग या विधि के अंदर और कोड के इस टुकड़े के साथ पाठ को अपडेट करें 'yourcontrol.Invoke (y = () => yourcontrol.Text = "new text"); "
एंटोनियो लेइट

4
@ डब्लूम यह एक सदस्य नहीं है क्योंकि यह केवल WinForms के लिए है। WPF के लिए आप Dispatcher.Invoke
sLw

1
मैं इस समाधान का पालन कर रहा था लेकिन कभी-कभी मेरा UI अपडेट नहीं हो रहा था। मैंने पाया कि मुझे this.refresh()जीयूआई को अमान्य करने और उसे फिर से लागू करने की आवश्यकता है .. अगर यह मददगार है ..
रकीबुल हक

137

.NET 3.5+ के लिए आग और भूल एक्सटेंशन विधि

using System;
using System.Windows.Forms;

public static class ControlExtensions
{
    /// <summary>
    /// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.
    /// </summary>
    /// <param name="control"></param>
    /// <param name="code"></param>
    public static void UIThread(this Control @this, Action code)
    {
        if (@this.InvokeRequired)
        {
            @this.BeginInvoke(code);
        }
        else
        {
            code.Invoke();
        }
    }
}

इसे कोड की निम्नलिखित पंक्ति का उपयोग करके बुलाया जा सकता है:

this.UIThread(() => this.myLabel.Text = "Text Goes Here");

5
@ के उपयोग की क्या बात है? "नियंत्रण" बराबर नहीं होगा? क्या @ के लिए कोई लाभ हैं?
अर्गेल

14
@jeromeyers - यह @thisकेवल चर नाम है, इस मामले में विस्तार को कॉल करने वाले वर्तमान नियंत्रण का संदर्भ है। आप इसका नाम बदलकर स्रोत कर सकते हैं, या जो भी आपकी नाव को तैरता है। मैं उपयोग करता हूं @this, क्योंकि यह 'इस नियंत्रण' का जिक्र है जो विस्तार को बुला रहा है और सामान्य (गैर-विस्तार) कोड में 'इस' कीवर्ड का उपयोग करने के साथ सुसंगत है (मेरे सिर में, कम से कम)।
स्टाइलएक्सवर

1
यह महान, आसान और मेरे लिए सबसे अच्छा समाधान है। आप ui थ्रेड में वे सभी काम शामिल कर सकते हैं जो आपको करने हैं। उदाहरण: this.UIThread (() => {txtMessage.Text = message; listBox1.Items.Add (संदेश);});
ऑटो

1
मुझे वास्तव में यह समाधान पसंद है। माइनर नाइट: मैं इसके OnUIThreadबजाय इस विधि का नाम दूंगा UIThread
टूलमेकर

2
इसलिए मैंने इस एक्सटेंशन का नाम रखा RunOnUiThread। लेकिन यह सिर्फ व्यक्तिगत स्वाद है।
ग्रिसग्राम

66

यह क्लासिक तरीका है जो आपको यह करना चाहिए:

using System;
using System.Windows.Forms;
using System.Threading;

namespace Test
{
    public partial class UIThread : Form
    {
        Worker worker;

        Thread workerThread;

        public UIThread()
        {
            InitializeComponent();

            worker = new Worker();
            worker.ProgressChanged += new EventHandler<ProgressChangedArgs>(OnWorkerProgressChanged);
            workerThread = new Thread(new ThreadStart(worker.StartWork));
            workerThread.Start();
        }

        private void OnWorkerProgressChanged(object sender, ProgressChangedArgs e)
        {
            // Cross thread - so you don't get the cross-threading exception
            if (this.InvokeRequired)
            {
                this.BeginInvoke((MethodInvoker)delegate
                {
                    OnWorkerProgressChanged(sender, e);
                });
                return;
            }

            // Change control
            this.label1.Text = e.Progress;
        }
    }

    public class Worker
    {
        public event EventHandler<ProgressChangedArgs> ProgressChanged;

        protected void OnProgressChanged(ProgressChangedArgs e)
        {
            if(ProgressChanged!=null)
            {
                ProgressChanged(this,e);
            }
        }

        public void StartWork()
        {
            Thread.Sleep(100);
            OnProgressChanged(new ProgressChangedArgs("Progress Changed"));
            Thread.Sleep(100);
        }
    }


    public class ProgressChangedArgs : EventArgs
    {
        public string Progress {get;private set;}
        public ProgressChangedArgs(string progress)
        {
            Progress = progress;
        }
    }
}

आपके कार्यकर्ता सूत्र में एक घटना है। आपका UI थ्रेड कार्य करने के लिए एक और थ्रेड प्रारंभ करता है और उस वर्कर ईवेंट को हुक करता है ताकि आप वर्कर थ्रेड की स्थिति प्रदर्शित कर सकें।

फिर यूआई में आपको वास्तविक नियंत्रण बदलने के लिए थ्रेड्स को पार करने की आवश्यकता है ... एक लेबल या प्रगति पट्टी की तरह।


62

उपयोग करने के लिए सरल उपाय है Control.Invoke

void DoSomething()
{
    if (InvokeRequired) {
        Invoke(new MethodInvoker(updateGUI));
    } else {
        // Do Something
        updateGUI();
    }
}

void updateGUI() {
    // update gui here
}

अच्छी तरह से सादगी के लिए किया! न केवल सरल, बल्कि अच्छी तरह से काम करता है! मुझे वास्तव में समझ में नहीं आया कि Microsoft इसे सरल क्यों नहीं बना सका क्योंकि इसका मतलब है! मुख्य सूत्र पर 1 पंक्ति को कॉल करने के लिए, हमें कुछ कार्यों को लिखना चाहिए!
एमबीएच

1
@ बीजिंग सहमत हैं। BTW, क्या आपने ऊपर stackoverflow.com/a/3588137/199364 उत्तर देखा है, जो एक विस्तार विधि को परिभाषित करता है? एक बार एक कस्टम यूटिलिटीज क्लास में करें, फिर किसी भी अधिक देखभाल करने की ज़रूरत नहीं है कि Microsoft ने हमारे लिए यह नहीं किया :)
टूलमेकर

@ ToolmakerSteve Thats वास्तव में इसका क्या मतलब था! आप सही हैं हम एक रास्ता खोज सकते हैं, लेकिन मेरा मतलब है DRY (अपने आप को दोहराएं नहीं) के दृष्टिकोण से, जिस समस्या का सामान्य समाधान है, उन्हें Microsoft द्वारा न्यूनतम प्रयास के साथ हल किया जा सकता है जो बहुत समय बचाएगा। प्रोग्रामर :)
MBH

47

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

    private void button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "A");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "B");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "C");
    }

    private void backgroundWorker1_ProgressChanged(
        object sender, 
        ProgressChangedEventArgs e)
    {
        label1.Text = e.UserState.ToString();
    }

यदि आप हमेशा एक ही फ़ील्ड को अपडेट करना चाहते हैं तो यह ठीक है। यदि आपको बनाने के लिए अधिक जटिल अपडेट मिल गए हैं, तो आप UI स्थिति का प्रतिनिधित्व करने के लिए एक वर्ग को परिभाषित कर सकते हैं और उसे ReportProgress विधि से पारित कर सकते हैं।

एक अंतिम बात, WorkerReportsProgressध्वज को स्थापित करना सुनिश्चित करें , या ReportProgressविधि को पूरी तरह से नजरअंदाज कर दिया जाएगा।


2
प्रसंस्करण के अंत में, उपयोगकर्ता इंटरफ़ेस को अपडेट करना भी संभव है backgroundWorker1_RunWorkerCompleted
डेविडआरआर

41

उत्तर के विशाल बहुमत का उपयोग Control.Invokeहोता है जो एक दौड़ की स्थिति होने की प्रतीक्षा कर रहा है । उदाहरण के लिए, स्वीकृत उत्तर पर विचार करें:

string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate { 
    someLabel.Text = newText; // runs on UI thread
});

उपयोगकर्ता प्रपत्र बस से पहले बंद कर देता है तो this.Invokeकहा जाता है (याद है, thisहै Form, एक वस्तु)ObjectDisposedException संभावना सक्रिय किए जाएंगे।

समाधान का उपयोग करना है SynchronizationContext, विशेष SynchronizationContext.Currentरूप से हैमिल्टन के रूप में। डेनियलब सुझाव देते हैं (अन्य उत्तर विशिष्ट SynchronizationContextकार्यान्वयन पर भरोसा करते हैं जो पूरी तरह अनावश्यक है)। मैं इसके SynchronizationContext.Postबजाय उपयोग करने के लिए उसके कोड को थोड़ा संशोधित करूंगा SynchronizationContext.Send(क्योंकि आम तौर पर इंतजार करने के लिए श्रमिक सूत्र की कोई आवश्यकता नहीं है):

public partial class MyForm : Form
{
    private readonly SynchronizationContext _context;
    public MyForm()
    {
        _context = SynchronizationContext.Current
        ...
    }

    private MethodOnOtherThread()
    {
         ...
         _context.Post(status => someLabel.Text = newText,null);
    }
}

ध्यान दें कि .NET 4.0 और ऊपर आपको वास्तव में async संचालन के लिए कार्यों का उपयोग करना चाहिए। समतुल्य कार्य-आधारित दृष्टिकोण (उपयोग करते हुए ) के लिए n- सान का उत्तर देखें TaskScheduler.FromCurrentSynchronizationContext

अंत में .NET 4.5 और ऊपर आप Ryszard D foregan द्वारा उन मामलों के लिए भी उपयोग कर सकते हैं Progress<T>(जो मूल रूप से SynchronizationContext.Currentइसके निर्माण पर कब्जा करते हैं ) उन मामलों के लिए जहां लंबे समय से चल रहे ऑपरेशन को यूआई कोड चलाने के लिए अभी भी काम करने की आवश्यकता है।


37

आपको यह सुनिश्चित करना होगा कि अपडेट सही थ्रेड पर होता है; यूआई धागा।

ऐसा करने के लिए, आपको सीधे कॉल करने के बजाय ईवेंट-हैंडलर को आमंत्रित करना होगा।

आप अपने ईवेंट को इस तरह बढ़ाकर ऐसा कर सकते हैं:

(कोड मेरे सिर से बाहर टाइप किया गया है, इसलिए मैंने सही सिंटैक्स आदि की जांच नहीं की है, लेकिन यह आपको जाना चाहिए।)

if( MyEvent != null )
{
   Delegate[] eventHandlers = MyEvent.GetInvocationList();

   foreach( Delegate d in eventHandlers )
   {
      // Check whether the target of the delegate implements 
      // ISynchronizeInvoke (Winforms controls do), and see
      // if a context-switch is required.
      ISynchronizeInvoke target = d.Target as ISynchronizeInvoke;

      if( target != null && target.InvokeRequired )
      {
         target.Invoke (d, ... );
      }
      else
      {
          d.DynamicInvoke ( ... );
      }
   }
}

ध्यान दें कि उपरोक्त कोड WPF परियोजनाओं पर काम नहीं करेगा, क्योंकि WPF नियंत्रण ISynchronizeInvokeइंटरफ़ेस को लागू नहीं करता है।

यह सुनिश्चित करने के लिए कि ऊपर दिया गया कोड विंडोज फॉर्म और डब्ल्यूपीएफ और अन्य सभी प्लेटफार्मों के साथ काम करता है, आप पर एक नज़र डाल सकते हैं AsyncOperation, AsyncOperationManagerऔरSynchronizationContext वर्गों।

इस तरह से घटनाओं को आसानी से बढ़ाने के लिए, मैंने एक एक्सटेंशन विधि बनाई है, जो मुझे सिर्फ कॉल करके किसी घटना को बढ़ाने के लिए सरल बनाने की अनुमति देता है:

MyEvent.Raise(this, EventArgs.Empty);

बेशक, आप BackGroundWorker वर्ग का उपयोग भी कर सकते हैं, जो आपके लिए इस मामले को अमूर्त कर देगा।


वास्तव में, लेकिन मुझे इस मामले के साथ अपने जीयूआई कोड को 'अव्यवस्था' करना पसंद नहीं है। मेरे GUI को परवाह नहीं करनी चाहिए कि उसे आह्वान करना है या नहीं। दूसरे शब्दों में: मुझे नहीं लगता कि यह संदर्भ-स्वाइप प्रदर्शन करने के लिए जीयूआई की जिम्मेदारी है।
फ्रेडरिक घीसेल्स

1
प्रतिनिधि को तोड़ना आदि अलग-अलग प्रतीत होता है - केवल क्यों नहीं: SynchronizationContext.Current.Send (प्रतिनिधि {MyEvent (...);}; null);
मार्क Gravell

क्या आपके पास हमेशा सिंक्रोनाइज़ेशन कॉनटेक्स्ट की पहुँच है? भले ही आपकी क्लास एक क्लास लिब में हो?
फ्रेडरिक घीसेल्स

29

आपको GUI थ्रेड पर विधि लागू करनी होगी। आप उस पर नियंत्रण करके कॉल कर सकते हैं।

उदाहरण के लिए:

delegate void UpdateLabelDelegate (string message);

void UpdateLabel (string message)
{
    if (InvokeRequired)
    {
         Invoke (new UpdateLabelDelegate (UpdateLabel), message);
         return;
    }

    MyLabelControl.Text = message;
}

1
इनवोक लाइन मुझे एक संकलक त्रुटि देता है। 'System.Windows.Forms.Control.Invoke (System.Delegate, ऑब्जेक्ट [])' के लिए सबसे अच्छा अतिभारित विधि मिलान में कुछ अमान्य तर्क हैं
क्रुएलियो

28

परिदृश्य की तुच्छता के कारण मैं वास्तव में स्थिति के लिए UI थ्रेड पोल होगा। मुझे लगता है कि आप पाएंगे कि यह काफी सुरुचिपूर्ण हो सकता है।

public class MyForm : Form
{
  private volatile string m_Text = "";
  private System.Timers.Timer m_Timer;

  private MyForm()
  {
    m_Timer = new System.Timers.Timer();
    m_Timer.SynchronizingObject = this;
    m_Timer.Interval = 1000;
    m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; };
    m_Timer.Start();
    var thread = new Thread(WorkerThread);
    thread.Start();
  }

  private void WorkerThread()
  {
    while (...)
    {
      // Periodically publish progress information.
      m_Text = "Still working...";
    }
  }
}

दृष्टिकोण marshaling आपरेशन का उपयोग करते समय आवश्यक से बचा जाता है ISynchronizeInvoke.Invokeऔर ISynchronizeInvoke.BeginInvokeतरीकों। मार्शलिंग तकनीक का उपयोग करने में कुछ भी गलत नहीं है, लेकिन कुछ ऐसे कैविएट हैं जिनसे आपको अवगत होने की आवश्यकता है।

  • सुनिश्चित करें कि आप BeginInvokeबहुत बार कॉल नहीं करते हैं या यह संदेश पंप से आगे निकल सकता है।
  • Invokeकार्यकर्ता सूत्र पर कॉल करना एक अवरुद्ध कॉल है। यह अस्थायी रूप से उस धागे में किए जा रहे काम को रोक देगा।

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

  • UI और वर्कर थ्रेड्स ढीले कपल के रूप में रहते हैं Control.Invokeया Control.BeginInvokeउनके विपरीत जो उन्हें कसकर जोड़े बनाते हैं।
  • UI थ्रेड वर्कर थ्रेड की प्रगति को बाधित नहीं करेगा।
  • वर्कर थ्रेड उस समय पर हावी नहीं हो सकता जब यूआई थ्रेड अपडेट करने में खर्च करता है।
  • जिन अंतरालों पर UI और वर्कर थ्रेड ऑपरेशन करते हैं वे स्वतंत्र रह सकते हैं।
  • वर्कर थ्रेड UI थ्रेड के संदेश पंप से आगे नहीं निकल सकता है।
  • यूआई थ्रेड को कब और कितनी बार यूआई अपडेट किया जाता है, यह तय करना है।

3
अच्छा विचार। केवल एक चीज जिसका आपने उल्लेख नहीं किया है कि एक बार वर्करट्रैड समाप्त होने के बाद आप टाइमर को कैसे ठीक से डिस्पोज़ करते हैं। ध्यान दें कि जब एप्लिकेशन समाप्त होता है (यानी उपयोगकर्ता एप्लिकेशन को बंद कर देता है) तो यह परेशानी पैदा कर सकता है। क्या आपको पता है कि इसे कैसे हल किया जाए?
मैट

@ मैट Elapsedघटना के लिए एक अनाम हैंडलर का उपयोग करने के बजाय , आप एक सदस्य विधि का उपयोग करते हैं ताकि आप टाइमर को हटा दें जब फार्म का निपटान किया जाता है ...
Phil1970

@ Phil1970 - अच्छी बात है। आप System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };इसे पसंद करते हैं और इसके माध्यम से असाइन करते हैं m_Timer.Elapsed += handler;, बाद में डिस्पोजल संदर्भ m_Timer.Elapsed -= handler;में मैं सही हूं? और सलाह के लिए यहाँ चर्चा के अनुसार निपटाने / बंद करने के लिए ।
मैट

27

पिछले उत्तरों में कोई भी इनवोक सामान आवश्यक नहीं है।

आपको WindowsFormsSynchronizationContext को देखना होगा:

// In the main thread
WindowsFormsSynchronizationContext mUiContext = new WindowsFormsSynchronizationContext();

...

// In some non-UI Thread

// Causes an update in the GUI thread.
mUiContext.Post(UpdateGUI, userData);

...

void UpdateGUI(object userData)
{
    // Update your GUI controls here
}

4
क्या आपको लगता है कि हुड के तहत पोस्ट विधि का उपयोग करता है? :)
09बदेलीबेली

23

यह एक .NET फ्रेमवर्क 3.0 का उपयोग करके ऊपर दिए गए समाधान के समान है, लेकिन इसने संकलन-समय सुरक्षा समर्थन के मुद्दे को हल किया ।

public  static class ControlExtension
{
    delegate void SetPropertyValueHandler<TResult>(Control souce, Expression<Func<Control, TResult>> selector, TResult value);

    public static void SetPropertyValue<TResult>(this Control source, Expression<Func<Control, TResult>> selector, TResult value)
    {
        if (source.InvokeRequired)
        {
            var del = new SetPropertyValueHandler<TResult>(SetPropertyValue);
            source.Invoke(del, new object[]{ source, selector, value});
        }
        else
        {
            var propInfo = ((MemberExpression)selector.Body).Member as PropertyInfo;
            propInfo.SetValue(source, value, null);
        }
    }
}

काम में लाना:

this.lblTimeDisplay.SetPropertyValue(a => a.Text, "some string");
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, false);

यदि उपयोगकर्ता गलत डेटा प्रकार पास करता है, तो कंपाइलर विफल हो जाएगा।

this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");

23

Salvete! इस प्रश्न के लिए खोज करने के बाद, मुझे फ्रैंकग और ओरेगॉन घोस्ट के जवाब मिले सबसे आसान लगे जो मेरे लिए सबसे उपयोगी थे। अब, मैं Visual Basic में कोड करता हूं और एक कनवर्टर के माध्यम से इस स्निपेट को चलाता हूं; इसलिए मुझे यकीन नहीं है कि यह कैसे बदल जाएगा।

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

और इसलिए, अब मैं पूरे प्रोग्राम में कहीं से भी एक लाइन के साथ डिस्प्ले को अपडेट कर सकता हूं, जिसमें आपको लगता है कि यह बिना थ्रेडिंग के बिना काम करेगा:

  form_Diagnostics.updateDiagWindow(whatmessage);

मुख्य कोड (इसे अपने फॉर्म के वर्ग कोड के अंदर डालें):

#region "---------Update Diag Window Text------------------------------------"
// This sub allows the diag window to be updated by all threads
public void updateDiagWindow(string whatmessage)
{
    var _with1 = diagwindow;
    if (_with1.InvokeRequired) {
        _with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage);
    } else {
        UpdateDiag(whatmessage);
    }
}
// This next line makes the private UpdateDiagWindow available to all threads
private delegate void UpdateDiagDelegate(string whatmessage);
private void UpdateDiag(string whatmessage)
{
    var _with2 = diagwindow;
    _with2.appendtext(whatmessage);
    _with2.SelectionStart = _with2.Text.Length;
    _with2.ScrollToCaret();
}
#endregion

21

कई उद्देश्यों के लिए यह इस रूप में सरल है:

public delegate void serviceGUIDelegate();
private void updateGUI()
{
  this.Invoke(new serviceGUIDelegate(serviceGUI));
}

"serviceGUI ()" फॉर्म (यह) के भीतर एक जीयूआई स्तर की विधि है जो आपके इच्छित कई नियंत्रणों को बदल सकती है। दूसरे थ्रेड से "updateGUI ()" को कॉल करें। मानों को पारित करने के लिए पैरामीटर जोड़े जा सकते हैं, या (शायद तेज) उन पर ताले के साथ वर्ग गुंजाइश चर का उपयोग कर सकते हैं यदि आवश्यक हो तो थ्रेड्स के बीच टकराव की कोई संभावना है जो उन्हें एक्सेस करने में अस्थिरता पैदा कर सकता है। यदि गैर-जीयूआई धागा महत्वपूर्ण है (ब्रायन गिदोन की चेतावनी को ध्यान में रखते हुए) तो इनवोक के बजाय बिगिन का उपयोग करें।


21

इयान केम्प के समाधान के मेरे सी # 3.0 भिन्नता में यह है:

public static void SetPropertyInGuiThread<C,V>(this C control, Expression<Func<C, V>> property, V value) where C : Control
{
    var memberExpression = property.Body as MemberExpression;
    if (memberExpression == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    var propertyInfo = memberExpression.Member as PropertyInfo;
    if (propertyInfo == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    if (control.InvokeRequired)
        control.Invoke(
            (Action<C, Expression<Func<C, V>>, V>)SetPropertyInGuiThread,
            new object[] { control, property, value }
        );
    else
        propertyInfo.SetValue(control, value, null);
}

आप इसे इस तरह कहते हैं:

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
  1. यह "के रूप में MemberExpression" के परिणाम के लिए अशक्त जाँच जोड़ता है।
  2. यह स्थैतिक प्रकार-सुरक्षा में सुधार करता है।

अन्यथा, मूल एक बहुत अच्छा समाधान है।


21
Label lblText; //initialized elsewhere

void AssignLabel(string text)
{
   if (InvokeRequired)
   {
      BeginInvoke((Action<string>)AssignLabel, text);
      return;
   }

   lblText.Text = text;           
}

ध्यान दें कि BeginInvoke()यह अधिक पसंद किया जाता है Invoke()क्योंकि यह गतिरोध पैदा करने की संभावना कम है (हालांकि, यह यहां कोई समस्या नहीं है जब सिर्फ एक लेबल के साथ पाठ असाइन करना है:

उपयोग करते समय Invoke() आप लौटने की विधि की प्रतीक्षा कर रहे हैं। अब, यह हो सकता है कि आप आह्वान किए गए कोड में कुछ ऐसा करें, जिसे थ्रेड के लिए प्रतीक्षा करने की आवश्यकता होगी, जो तुरंत स्पष्ट नहीं हो सकता है अगर यह कुछ कार्यों में दफन हो जाता है जिसे आप कॉल कर रहे हैं, जो स्वयं अप्रत्यक्ष रूप से ईवेंट हैंडलर के माध्यम से हो सकता है। तो आप धागे का इंतजार कर रहे होंगे, धागा आपका इंतजार कर रहा होगा और आप गतिरोध में हैं।

यह वास्तव में हमारे जारी किए गए कुछ सॉफ्टवेयरों को लटका देने का कारण बना। के Invoke()साथ बदलकर ठीक करना काफी आसान था BeginInvoke()। जब तक आपको सिंक्रोनस ऑपरेशन की आवश्यकता नहीं होती है, जो कि यदि आपको रिटर्न वैल्यू, उपयोग की आवश्यकता हो तो मामला हो सकता है BeginInvoke()


20

जब मैंने उसी मुद्दे का सामना किया, तो मैंने Google से मदद मांगी, लेकिन मुझे एक सरल समाधान देने के बजाय इसने MethodInvokerऔर ब्ला ब्ला ब्ला का उदाहरण देकर मुझे और भ्रमित कर दिया । इसलिए मैंने इसे अपने दम पर हल करने का फैसला किया। यहाँ मेरा समाधान है:

इस तरह एक प्रतिनिधि बनाएं:

Public delegate void LabelDelegate(string s);

void Updatelabel(string text)
{
   if (label.InvokeRequired)
   {
       LabelDelegate LDEL = new LabelDelegate(Updatelabel);
       label.Invoke(LDEL, text);
   }
   else
       label.Text = text
}

आप इस फ़ंक्शन को नए थ्रेड में इस तरह से कॉल कर सकते हैं

Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();

साथ भ्रमित मत हो Thread(() => .....)। जब मैं किसी थ्रेड पर काम करता हूं तो मैं एक अनाम फ़ंक्शन या लैम्ब्डा अभिव्यक्ति का उपयोग करता हूं। कोड की पंक्तियों को कम करने के लिए आप उस ThreadStart(..)विधि का भी उपयोग कर सकते हैं जिसे मैं यहाँ नहीं समझाता।


17

बस कुछ इस तरह का उपयोग करें:

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });

यदि आपके पास है e.ProgressPercentage, तो क्या आप पहले से ही यूआई थ्रेड में नहीं हैं जिस पद्धति से आप इसे कॉल कर रहे हैं?
लार्सटेक

ProgressChanged ईवेंट UI थ्रेड पर चलता है। यह BackgroundWorker के उपयोग की उपयुक्तताओं में से एक है। पूरा कार्यक्रम गुई पर भी चलता है। नॉन-यूआई थ्रेड में चलने वाली एकमात्र चीज DoWork विधि है।
लार्सटेक

15

आप पहले से मौजूद प्रतिनिधि का उपयोग कर सकते हैं Action:

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}

14

मेरा संस्करण पुनरावर्ती "मंत्र" की एक पंक्ति सम्मिलित करना है :

बिना किसी तर्क के:

    void Aaaaaaa()
    {
        if (InvokeRequired) { Invoke(new Action(Aaaaaaa)); return; } //1 line of mantra

        // Your code!
    }

एक फ़ंक्शन के लिए जिसमें तर्क हैं:

    void Bbb(int x, string text)
    {
        if (InvokeRequired) { Invoke(new Action<int, string>(Bbb), new[] { x, text }); return; }
        // Your code!
    }

कि यह है


कुछ तर्क : आमतौर पर कोड की पठनीयता के बाद {} को खराब कर दिया जाता हैif () एक लाइन में स्टेटमेंट के । लेकिन इस मामले में यह सभी के लिए समान है "मंत्र"। यदि यह विधि प्रोजेक्ट के अनुरूप है तो यह कोड पठनीयता को नहीं तोड़ता है। और यह आपके कोड को कूड़े (पांच के बजाय कोड की एक पंक्ति) से बचाता है।

जैसा कि आप देखते if(InvokeRequired) {something long}हैं कि आप जानते हैं "यह फ़ंक्शन किसी अन्य थ्रेड से कॉल करने के लिए सुरक्षित है"।


13

इस का उपयोग करके लेबल को ताज़ा करने का प्रयास करें

public static class ExtensionMethods
{
    private static Action EmptyDelegate = delegate() { };

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}

क्या यह विंडोज फॉर्म के लिए है ?
१३:१३ बजे कीकनेट

13

एक वर्ग चर बनाएँ:

SynchronizationContext _context;

इसे उस निर्माता में सेट करें जो आपका UI बनाता है:

var _context = SynchronizationContext.Current;

जब आप लेबल को अपडेट करना चाहते हैं:

_context.Send(status =>{
    // UPDATE LABEL
}, null);

12

आपको इनवोक और प्रतिनिधि का उपयोग करना चाहिए

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });

12

इस प्रश्न पर अधिकांश अन्य उत्तर मेरे लिए थोड़े जटिल हैं (मैं C # में नया हूं), इसलिए मैं अपना लिख ​​रहा हूं:

मेरे पास WPF एप्लिकेशन है और नीचे एक कार्यकर्ता को परिभाषित किया है:

मुद्दा:

BackgroundWorker workerAllocator;
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1) {
    // This is my DoWork function.
    // It is given as an anonymous function, instead of a separate DoWork function

    // I need to update a message to textbox (txtLog) from this thread function

    // Want to write below line, to update UI
    txt.Text = "my message"

    // But it fails with:
    //  'System.InvalidOperationException':
    //  "The calling thread cannot access this object because a different thread owns it"
}

समाधान:

workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1)
{
    // The below single line works
    txtLog.Dispatcher.BeginInvoke((Action)(() => txtLog.Text = "my message"));
}

मुझे अभी तक यह पता नहीं चल पाया है कि उपरोक्त लाइन का क्या मतलब है, लेकिन यह काम करता है।

के लिए WinForms :

समाधान:

txtLog.Invoke((MethodInvoker)delegate
{
    txtLog.Text = "my message";
});

सवाल Winform के बारे में था, WPF के बारे में नहीं।
मार्क एल।

धन्यवाद। ऊपर WinForms समाधान जोड़ा गया।
मनोहर रेड्डी ने

... जो इस प्रश्न पर केवल कई अन्य उत्तरों की एक प्रति है, लेकिन ठीक है। समाधान का हिस्सा क्यों न बनें और अपना उत्तर हटा दें?
मार्क एल।

हम्म, आप सही हैं, यदि केवल, आपने मेरा उत्तर ध्यान से पढ़ा है, तो शुरुआती भाग (कारण मैंने उत्तर लिखा है), और उम्मीद है कि थोड़ा और ध्यान से आप देखें कि कोई ऐसा व्यक्ति है जिसके लिए आज भी वही समस्या थी और आज तक कायम है। मेरा सरल जवाब है, और इससे भी अधिक attn के साथ अगर आप असली कहानी को समझ सकते हैं कि यह सब क्यों हुआ, तो मुझे wpf के लिए खोज करने पर भी Google मुझे यहां भेजता है। जब से आप इन कम या ज्यादा स्पष्ट 3 कारणों से चूक गए हैं, मैं समझ सकता हूं कि आप अपने पतन को क्यों नहीं हटाएंगे। ठीक एक को साफ करने के बजाय, कुछ नया बनाएं जो अधिक कठिन हो।
मनोहर रेड्डी ने


8

उदाहरण के लिए, वर्तमान थ्रेड के अलावा किसी अन्य नियंत्रण को एक्सेस करें:

Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
    lblThreshold.Text = Speed_Threshold.ToString();
}));

वहाँ lblThresholdएक लेबल है और Speed_Thresholdएक वैश्विक चर रहा है।


8

जब आप UI थ्रेड में होते हैं, तो आप इसे इसके सिंक्रनाइज़ेशन संदर्भ कार्य शेड्यूलर के लिए पूछ सकते हैं। यह आपको एक टास्कशील्ड दे देता है जो UI थ्रेड पर सब कुछ शेड्यूल करता है।

फिर आप अपने कार्यों को श्रृंखलाबद्ध कर सकते हैं ताकि जब परिणाम तैयार हो जाए तो एक और कार्य (जो कि यूआई थ्रेड पर निर्धारित है) इसे चुनता है और इसे एक लेबल पर असाइन करता है।

public partial class MyForm : Form
{
  private readonly TaskScheduler _uiTaskScheduler;
  public MyForm()
  {
    InitializeComponent();
    _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  }

  private void buttonRunAsyncOperation_Click(object sender, EventArgs e)
  {
    RunAsyncOperation();
  }

  private void RunAsyncOperation()
  {
    var task = new Task<string>(LengthyComputation);
    task.ContinueWith(antecedent =>
                         UpdateResultLabel(antecedent.Result), _uiTaskScheduler);
    task.Start();
  }

  private string LengthyComputation()
  {
    Thread.Sleep(3000);
    return "47";
  }

  private void UpdateResultLabel(string text)
  {
    labelResult.Text = text;
  }
}

यह कार्यों के लिए काम करता है (धागे नहीं) जो अब समवर्ती कोड लिखने का पसंदीदा तरीका है


1
कॉलिंग Task.Startआमतौर पर एक अच्छा अभ्यास नहीं है blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspx
Schneider

8

मैं सिर्फ जवाब पढ़ता हूं और यह बहुत ही हॉट विषय प्रतीत होता है। मैं वर्तमान में .NET 3.5 SP1 और Windows फ़ॉर्म का उपयोग कर रहा हूं।

जाने-माने फार्मूले को पिछले उत्तरों में बहुत वर्णित किया गया है, जो कि इनवोक्रेसी का उपयोग करता है प्रॉपर्टी करता है, ज्यादातर मामलों को कवर करता है, लेकिन पूरे पूल को नहीं।

यदि अभी तक हैंडल नहीं बनाया गया है तो क्या होगा ?

InvokeRequired संपत्ति, के रूप में वर्णित यहाँ (MSDN को Control.InvokeRequired संपत्ति संदर्भ) TRUE देता कॉल एक धागा है कि जीयूआई धागा नहीं है, झूठी या तो करता है, तो कॉल जीयूआई धागे से बनाया गया था से बनाया गया था, या यदि हैंडल था अभी तक नहीं बनाया गया।

यदि आप किसी अन्य थ्रेड द्वारा दिखाए गए और अद्यतित मोडल रूप को चाहते हैं, तो आप एक अपवाद के पार आ सकते हैं। क्योंकि आप चाहते हैं कि वह रूप सामान्य रूप से दिखाया गया हो, आप निम्न कार्य कर सकते हैं:

private MyForm _gui;

public void StartToDoThings()
{
    _gui = new MyForm();
    Thread thread = new Thread(SomeDelegate);
    thread.Start();
    _gui.ShowDialog();
}

और प्रतिनिधि GUI पर एक लेबल अपडेट कर सकता है:

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.InvokeRequired)
        _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
    else
        _gui.Label1.Text = "Done!";
}

यह एक InvalidOperationException का कारण बन सकता है यदि लेबल के अपडेट से पहले संचालन "कम समय ले" (इसे पढ़ें और इसे एक सरलीकरण के रूप में व्याख्या करें) फॉर्म के हैंडल को बनाने के लिए GUI थ्रेड के लिए समय लगता है । यह ShowDialog () पद्धति के भीतर होता है ।

आपको इस तरह से हैंडल की जांच करनी चाहिए :

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.IsHandleCreated)  //  <---- ADDED
        if(_gui.InvokeRequired)
            _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
        else
            _gui.Label1.Text = "Done!";
}

यदि आप अभी तक हैंडल नहीं बनाए गए हैं, तो आप यह करने के लिए ऑपरेशन को संभाल सकते हैं: आप बस जीयूआई अपडेट को अनदेखा कर सकते हैं (जैसे ऊपर दिए गए कोड में दिखाया गया है) या आप प्रतीक्षा कर सकते हैं (अधिक जोखिम भरा)। इस सवाल का जवाब देना चाहिए।

वैकल्पिक सामान: व्यक्तिगत रूप से मैं निम्नलिखित कोडिंग आया था:

public class ThreadSafeGuiCommand
{
  private const int SLEEPING_STEP = 100;
  private readonly int _totalTimeout;
  private int _timeout;

  public ThreadSafeGuiCommand(int totalTimeout)
  {
    _totalTimeout = totalTimeout;
  }

  public void Execute(Form form, Action guiCommand)
  {
    _timeout = _totalTimeout;
    while (!form.IsHandleCreated)
    {
      if (_timeout <= 0) return;

      Thread.Sleep(SLEEPING_STEP);
      _timeout -= SLEEPING_STEP;
    }

    if (form.InvokeRequired)
      form.Invoke(guiCommand);
    else
      guiCommand();
  }
}

मैं अपने रूपों को फ़ीड करता हूं जो इस थ्रेडसैफ़ग्यूइकोमैंड के उदाहरण के साथ एक और थ्रेड द्वारा अद्यतन किए जाते हैं , और मैं उन तरीकों को परिभाषित करता हूं जो इस तरह से जीयूआई (मेरे फॉर्म में) को अपडेट करते हैं:

public void SetLabeTextTo(string value)
{
  _threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; });
}

इस तरह से मुझे पूरा यकीन है कि मैं अपने जीयूआई को अपडेट करूंगा जो भी थ्रेड कॉल करेगा, वैकल्पिक रूप से एक अच्छी तरह से परिभाषित राशि (टाइमआउट) की प्रतीक्षा कर रहा है।


1
यह खोजने के लिए यहाँ आया, क्योंकि मैं भी इश्सलेक्रिएट के लिए जाँच करता हूँ। जाँच करने के लिए एक अन्य संपत्ति IsDisposed है। यदि आपका फॉर्म निपटाया जाता है, तो आप उस पर इनवोक () कॉल नहीं कर सकते। यदि उपयोगकर्ता आपके बैकग्राउंड थ्रेड को पूरा करने से पहले फॉर्म को बंद कर देता है तो आप यह नहीं चाहते हैं कि फॉर्म के डिस्पोज होने पर वह यूआई को वापस कॉल करने की कोशिश करे।
जॉन

मैं कहूंगा कि इसके साथ शुरुआत करना एक बुरा विचार है ... आम तौर पर, आप बच्चे को तुरंत फॉर्म दिखाते हैं और बैकग्राउंड प्रोसेसिंग करते समय एक प्रगति पट्टी या कुछ अन्य प्रतिक्रिया होती है। या आप पहले सभी प्रसंस्करण करते हैं और फिर परिणाम को निर्माण के नए रूप में पास करते हैं। एक ही समय में दोनों करने से आम तौर पर मामूली लाभ होगा लेकिन बहुत कम बनाए रखने योग्य कोड।
फिल 1970

वर्णित परिदृश्य पृष्ठभूमि-थ्रेड जॉब के प्रगति दृश्य के रूप में उपयोग किए जाने वाले मोडल रूप को ध्यान में रखता है । क्योंकि यह मोडल होना चाहिए, इसे Form.ShowDialog () विधि को कॉल करके दिखाया जाना चाहिए । ऐसा करने से, आप अपने कोड को रोकते हैं जो फॉर्म बंद होने तक निष्पादित होने वाली कॉल का अनुसरण करता है। इसलिए, जब तक कि आप दिए गए उदाहरण से अलग से बैकग्राउंड थ्रेड शुरू नहीं कर सकते (और, निश्चित रूप से, आप कर सकते हैं) बैकग्राउंड थ्रेड शुरू होने के बाद इस फॉर्म को मामूली दिखाया जाना चाहिए। इस मामले में, आपको बनाए जाने के लिए हैंडल की जांच करने की आवश्यकता है। यदि आपको एक मोडल रूप की आवश्यकता नहीं है, तो यह एक और कहानी है।
Sume
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.