c# मैं जीयूआई को किसी अन्य थ्रेड से कैसे अपडेट करूं?




.net multithreading (24)

Label को किसी अन्य थ्रेड से अपडेट करने का सबसे आसान तरीका क्या है?

मेरे पास thread1 पर एक Form है, और उस से मैं एक और थ्रेड (थ्रेड thread2 ) शुरू कर रहा हूं। जबकि thread2 कुछ फाइलों को संसाधित कर रहा है, मैं thread2 के काम की वर्तमान स्थिति के साथ Form पर एक Label अपडेट करना चाहता हूं।

मैं उसे कैसे कर सकता हूँ?


Even if the operation is time-consuming (thread.sleep in my example) - This code will NOT lock your UI:

 private void button1_Click(object sender, EventArgs e)
 {

      Thread t = new Thread(new ThreadStart(ThreadJob));
      t.IsBackground = true;
      t.Start();         
 }

 private void ThreadJob()
 {
     string newValue= "Hi";
     Thread.Sleep(2000); 

     this.Invoke((MethodInvoker)delegate
     {
         label1.Text = newValue; 
     });
 }

Create a class variable:

SynchronizationContext _context;

Set it in the constructor that creates your UI:

var _context = SynchronizationContext.Current;

When you want to update the label:

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

यह .NET Framework 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");

You must use invoke and delegate

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

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

आपको 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
}

उत्तर के विशाल बहुमत 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 hamilton.danielb रूप में सुझाव देता है (अन्य उत्तरों विशिष्ट SynchronizationContext कार्यान्वयन पर भरोसा करते हैं जो पूरी तरह से अनावश्यक है)। मैं SynchronizationContext.Post बजाय SynchronizationContext.Post का उपयोग करने के लिए अपने कोड को थोड़ा संशोधित करता हूं। हालांकि (जैसा कि आमतौर पर कार्यकर्ता धागे को प्रतीक्षा करने की आवश्यकता नहीं है):

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 और ऊपर आपको वास्तव में एसिंक ऑपरेशंस के लिए कार्यों का उपयोग करना चाहिए। समकक्ष कार्य-आधारित दृष्टिकोण ( TaskScheduler.FromCurrentSynchronizationContext का उपयोग TaskScheduler.FromCurrentSynchronizationContext ) के लिए n-san's उत्तर देखें।

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


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

मेरे पास form_Diagnostics, नामक एक संवाद फ़ॉर्म है form_Diagnostics, जिसमें एक updateDiagWindow, बॉक्स है, जिसे updateDiagWindow, कहा जाता है 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

The easiest way I think:

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }

आपको GUI थ्रेड पर विधि को आमंत्रित करने की आवश्यकता होगी। आप Control.Invoke को कॉल करके ऐसा कर सकते हैं।

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

delegate void UpdateLabelDelegate (string message);

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

    MyLabelControl.Text = message;
}

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

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

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

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

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

  • यूआई और वर्कर थ्रेड Control.Invoke विपरीत के रूप में कम से कम युग्मित रहते हैं। Control.Invoke या Control.BeginInvokeControl.Invoke Control.BeginInvoke दृष्टिकोण जो उन्हें कसकर जोड़ता है।
  • यूआई थ्रेड कार्यकर्ता धागे की प्रगति में बाधा नहीं डालेगा।
  • यूआई थ्रेड अद्यतन करने के समय कार्यकर्ता धागा हावी नहीं हो सकता है।
  • अंतराल जिस पर यूआई और कार्यकर्ता धागे संचालन करते हैं स्वतंत्र रह सकते हैं।
  • वर्कर थ्रेड यूआई थ्रेड के संदेश पंप को ओवरराउन नहीं कर सकता है।
  • यूआई थ्रेड को निर्देश दिया जाता है कि यूआई कब और कितनी बार अपडेट हो जाता है।

थ्रेडिंग कोड अक्सर छोटी होती है और परीक्षण करने में हमेशा मुश्किल होती है। पृष्ठभूमि कार्य से उपयोगकर्ता इंटरफ़ेस को अपडेट करने के लिए आपको थ्रेडिंग कोड लिखने की आवश्यकता नहीं है। उपयोगकर्ता इंटरफ़ेस को अपडेट करने के लिए कार्य और इसकी 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();
    }

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

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


I wanted to add a warning because I noticed that some of the simple solutions omit the InvokeRequired check.

I noticed that if your code executes before the window handle of the control has been created (eg before the form is shown), Invoke throws an exception. So I recommend always checking on InvokeRequired before calling Invoke or BeginInvoke .


सरल समाधान Control.Invoke का उपयोग करना है।

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

void updateGUI() {
    // update gui here
}

You may use the already-existing delegate Action :

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

Label lblText; //initialized elsewhere

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

   lblText.Text = text;           
}

ध्यान दें कि BeginInvoke() को Invoke() पर प्राथमिकता दी जाती है क्योंकि यह Invoke() का कारण बनने की संभावना कम है (हालांकि, यह लेबल पर टेक्स्ट निर्दिष्ट करते समय यहां कोई समस्या नहीं है):

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

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


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

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

आप अपनी घटना को इस तरह उठाकर ऐसा कर सकते हैं:

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

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 AsyncOperationManager क्लासेस को देख सकते हैं।

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

MyEvent.Raise(this, EventArgs.Empty);

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


.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 ||
      [email protected]().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 संस्करण से काफी बेहतर है।

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


Try to refresh the label using this

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

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

जब मुझे एक ही मुद्दे का सामना करना पड़ा तो मैंने 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
}

You can call this function in a new thread like this

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

Don't be confused with Thread(() => .....) . I use an anonymous function or lambda expression when I work on a thread. To reduce the lines of code you can use the ThreadStart(..) method too which I am not supposed to explain here.


.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");

I just read the answers and this appears to be a very hot topic. I'm currently using .NET 3.5 SP1 and Windows Forms.

The well-known formula greatly described in the previous answers that makes use of the InvokeRequired property covers most of the cases, but not the entire pool.

What if the Handle has not been created yet?

The InvokeRequired property, as described here (Control.InvokeRequired Property reference to MSDN) returns true if the call was made from a thread that is not the GUI thread, false either if the call was made from the GUI thread, or if the Handle was not created yet.

You can come across an exception if you want to have a modal form shown and updated by another thread. Because you want that form shown modally, you could do the following:

private MyForm _gui;

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

And the delegate can update a Label on the 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!";
}

This can cause an InvalidOperationException if the operations before the label's update "take less time" (read it and interpret it as a simplification) than the time it takes for the GUI thread to create the Form 's Handle . This happens within the ShowDialog() method.

You should also check for the Handle like this:

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

You can handle the operation to perform if the Handle has not been created yet: You can just ignore the GUI update (like shown in the code above) or you can wait (more risky). This should answer the question.

Optional stuff: Personally I came up coding the following:

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

I feed my forms that get updated by another thread with an instance of this ThreadSafeGuiCommand , and I define methods that update the GUI (in my Form) like this:

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

In this way I'm quite sure that I will have my GUI updated whatever thread will make the call, optionally waiting for a well-defined amount of time (the timeout).


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

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

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

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


यह इयान केम्प के समाधान के मेरे सी # 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. यह "सदस्य एक्सप्रेशन" के परिणामस्वरूप नल-जांच जोड़ता है।
  2. यह स्थिर प्रकार-सुरक्षा में सुधार करता है।

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


For example, access a control other than in the current thread:

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

There the lblThreshold is a Label and Speed_Threshold is a global variable.





user-interface