【问题标题】:Solve a cross-threading Exception in WinForms解决 WinForms 中的跨线程异常
【发布时间】:2011-05-03 11:30:38
【问题描述】:

目前我正在使用 WinForms(在 C# 中),我必须在后台运行应用程序。为此,我使用异步。当我运行应用程序时,它会显示一个异常,例如

“跨线程操作无效:控件''从创建它的线程以外的线程访问。”

我该如何解决这个错误?

【问题讨论】:

标签: c# winforms multithreading


【解决方案1】:

当对控件进行方法调用时,如果调用者在与创建控件的线程不同的线程上,则需要使用Control.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.Invoke(new UpdateControlsDelegate(UpdateControls));
    }
    else
    {
        UpdateControls();
    }
}

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

希望对你有帮助。

【讨论】:

  • 您好,感谢您的回复,我正在尝试您提供的代码,实际上我在 button_click 事件中调用了 thred..但它总是转到 else 部分..如果 ( this.InvokeRequired) { this.Invoke(new PerformSomeActionDelegate(MyAction)); }
  • @Victor:这意味着此时不需要 Invoke。确保线程中执行的所有代码都执行“安全”(Invoke)调用以更新表单的控件。
  • @Victor:恐怕不可能
【解决方案2】:

通常,使用 WinForms 执行此类操作的最佳方法是使用 BackgroundWorker,它将在后台线程上运行您的工作,但为您提供了一种将状态报告回 UI 的简洁方式。

在许多日常 .NET 编程中,显式创建线程或调用 .Invoke 表明您没有充分利用该框架(当然,做低级工作有很多正当理由同样,只是人们有时意识到它们不太常见)。

【讨论】:

  • 感谢您的回复,我正在我的应用程序中使用这个(你在上面给出的链接)背景代码。但这显示错误......
  • @Victor - 您必须要么使用 .Invoke (参见其他人的示例),要么使用 BackgroundWorker.ReportProgress 方法(以及它引发的 ProgressChanged 事件)来访问表单。如果我使用的是 BackgroundWorker,我总是更喜欢 ReportProgress 而不是直接调用 Invoke。
【解决方案3】:

您需要检查您尝试更新的控件是否需要 Invoke。像这样的:

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

【讨论】:

    【解决方案4】:

    从 Invoke 更新为开始 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
    }
    

    【讨论】:

    • 嗨,Pankaj 我已经尝试过这段代码,但是当它是真的时,invokerequired 总是假的......我在我的按钮点击事件中调用了invokerequired.....
    • 用你的 buttonid 替换它并再次检查。
    • 我写了这样的代码 private void btnsiteranks_Click_1(object sender, EventArgs e) { InvokeUpdateControls(); } public void InvokeUpdateControls() { if (this.InvokeRequired) { this.BeginInvoke(new PerformSomeActionDelegate(Geturls)); } 其他 { Geturls(); } }
    • 好的,现在它工作正常,只是我更改了 if(!this.InvokeRequired) 之类的代码......但是我想在数据处理时显示标签文本如何......请帮助我
    • @Pankaj 你的InvokeUpdateControls 有点危险。如果需要调用,那么您将异步执行委托,即您将其安排在以后的时间,但如果您在同一个线程上(或尚未创建窗口句柄),则您正在执行它本身。这可能需要也可能不需要,但应该牢记差异。
    【解决方案5】:

    您可能会发现一种有用的模式是在与 GUI 交互的函数的顶部进行检查,以查看您是否在正确的线程上运行,并在需要时让函数自行调用。像这样:

        public delegate void InvocationDelegate();
    
        public void DoGuiStuff(){
          if (someControl.InvokeRequired){
            someControl.Invoke(InvocationDelegate(DoGuiStuff));
            return;  
          }
    
          //GUI manipulation here
        }
    

    使用这种模式 - 如果您在调用该方法时位于正确的线程上,则它不会调用自身,但如果您在不同的线程上,它会调用自身然后返回(因此 GUI 操作逻辑只是永远无论哪种方式都调用一次)。

    【讨论】:

      【解决方案6】:

      BeginInvoke

      这是防止跨线程异常的好方法。我在《C#程序员学习指南(MCSD)》一书中读到它

      您可以使用 BeginInvoke

      BeginInvoke 方法用于从其他线程更改 UI 控件的值。它以线程安全的方式进行。它需要一个代表;它告诉哪个 UI 控件需要更改其值。

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

      label1.Text 的值应改为“Hello”,不会出现异常,因为它是线程安全操作。

      【讨论】:

        【解决方案7】:

        UI 改变可以通过 Control.Invoke() 方法来完成,这个跨线程异常可以使用下面的代码 sn-p 来解决。

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

        【讨论】:

          【解决方案8】:

          我知道这个话题已有 10 年的历史了,但我想通过 lambda 选择器而不是定义每种类型的 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);
              }  
          

          或静态

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

          已通过 lambda 选择了来自 here 的用于为属性赋值的 GetSetter 方法

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

          那么使用就很简单了

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

          【讨论】:

            猜你喜欢
            • 2014-01-02
            • 1970-01-01
            • 2010-09-05
            • 1970-01-01
            • 2015-09-19
            • 1970-01-01
            • 1970-01-01
            • 2015-02-10
            • 1970-01-01
            相关资源
            最近更新 更多