开发者

Solve a cross-threading Exception in WinForms

Presently I'm working with WinForms(in C#) and I have to run the application in the background. For this purpose I'm using asynchronous. When I run the application it's showing an exception like

"Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on."

How can I开发者_如何学JAVA solve this error?


When making method calls to a control, if the caller is on a different thread than the one the control was created on, you need to call using Control.Invoke. Here is a code sample:

// you can define a delegate with the signature you want
public delegate void UpdateControlsDelegate();

public void SomeMethod()
{
    //this method is executed by the background worker
    InvokeUpdateControls();
}

public void InvokeUpdateControls()
{
    if (this.InvokeRequired)
    {
        this.Invoke(new UpdateControlsDelegate(UpdateControls));
    }
    else
    {
        UpdateControls();
    }
}

private void UpdateControls()
{
    // update your controls here
}

Hope it helps.


Most often, the best way to do this sort of thing with WinForms is to use BackgroundWorker, which will run your work on a background thread, but provide you with a nice clean way to report status back to the UI.

In a lot of everyday .NET programming, explicitly creating threads or calling .Invoke is a sign that you're not using the framework to its full advantage (of course, there are lots of legitimate reasons to do low-level stuff too, it's just that they're less common that people sometimes realise).


You need to check if Invoke is required for the control you're trying to update. Something like this:

Action<Control, string> setterCallback = (toSet, text) => toSet.Text = text;

void SetControlText(Control toSet, string text) {
  if (this.InvokeRequired) {
    this.Invoke(setterCallback, toSet, text);
  }
  else {
    setterCallback(toSet, text);
  }
}


Updated from Invoke to begin Invoke

// you can define a delegate with the signature you want
public delegate void UpdateControlsDelegate();

public void SomeMethod()
{
    //this method is executed by the background worker
    InvokeUpdateControls();
}

public void InvokeUpdateControls()
{
    if (this.InvokeRequired)
    {
        this.BeginInvoke(new UpdateControlsDelegate(UpdateControls));
    }
    else
    {
        UpdateControls();
    }
}

private void UpdateControls()
{
    // update your controls here
}


A pattern you might find useful is to do a check at the top of functions that interact with the GUI to see whether you are running on the correct thread or not and have the function invoke itself if required. Like this:

    public delegate void InvocationDelegate();

    public void DoGuiStuff(){
      if (someControl.InvokeRequired){
        someControl.Invoke(InvocationDelegate(DoGuiStuff));
        return;  
      }

      //GUI manipulation here
    }

Using this pattern - if you are on the correct thread when the method is called it doesn't invoke itself, but if you are on a different thread it will invoke itself and then return (so the GUI manipulation logic is only ever called once either way).


The UI changes can be done with Control.Invoke() methods, this cross thread exception can be solved using below code snippet.

void UpdateWorker()
{
   //Here ddUser is the user control
   //Action to be performed should be called within { } as like below code
   if (this.ddUser.InvokeRequired)
      ddUser.Invoke(new MethodInvoker(() => { ddUser.Size = new Size(100, 100); }));
}


I knew the topic is 10 years old, but I would like to improve the solution for generic through lambda selector instead of defining of each type of setter

    private void SetControlSafety<C, V>(C control, Expression<Func<C, V>> selector, V value)
    {
        if (this.InvokeRequired)
            this.Invoke(MyUtils.GetSetter(selector), control, value);
        else
            DataCrawlerUtils.GetSetter(selector)(control, value);
    }  

Or static

    public static void SetControlSafety<C, V>(C control, Expression<Func<C, V>> selector, V value) where C : Control
    {
        if (control.InvokeRequired)
            control.Invoke(DataCrawlerUtils.GetSetter(selector), control, value);
        else
            DataCrawlerUtils.GetSetter(selector)(control, value);
    }

GetSetter method from here to assign value to a property has been selected through lambda

    public static Action<T, TProperty> GetSetter<T, TProperty>(
       Expression<Func<T, TProperty>> pExpression
    )
    {
        var parameter1 = Expression.Parameter(typeof(T));
        var parameter2 = Expression.Parameter(typeof(TProperty));

        // turning an expression body into a PropertyInfo is common enough
        // that it's a good idea to extract this to a reusable method
        var member = (MemberExpression)pExpression.Body;
        var propertyInfo = (PropertyInfo)member.Member;

        // use the PropertyInfo to make a property expression
        // for the first parameter (the object)
        var property = Expression.Property(parameter1, propertyInfo);

        // assignment expression that assigns the second parameter (value) to the property
        var assignment = Expression.Assign(property, parameter2);

        // then just build the lambda, which takes 2 parameters, and has the assignment
        // expression for its body
        var setter = Expression.Lambda<Action<T, TProperty>>(
           assignment,
           parameter1,
           parameter2
        );

        return setter.Compile();
    }

Then the using is pretty simple

        SetControlSafety(txtStatus, x => x.Text, "Loading resources...");


BeginInvoke

It is a good way to prevent a cross-thread exception. I read it in a book "The C# Programmer’s Study Guide (MCSD"

You can use BeginInvoke

BeginInvoke method is used to change values of UI control from other threads. It does it in a thread-safe way. It requires a delegate; it tells which UI control needs to change its value.

  private async void button1_Click(object sender, EventArgs e)
   {
     Task task = Task.Run(() =>
        {
           this.BeginInvoke(new Action(() =>
               {
                  label1.Text = "Hello";
                  }));
         });
      await task;
  }

The value of label1.Text shall be changed to “Hello” and no exception will arise because it’s a threadsafe operation.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜