跨线程操作无效:从创建它的线程以外的线程访问控制

发布于 2024-07-07 00:10:45 字数 1538 浏览 7 评论 0原文

我有一个场景。 (Windows 窗体、C#、.NET)

  1. 有一个主窗体托管一些用户控件。
  2. 用户控件执行一些繁重的数据操作,因此,如果我直接调用 UserControl_Load 方法,UI 在加载方法执行期间将变得无响应。
  3. 为了克服这个问题,我在不同的线程上加载数据(尝试尽可能少地更改现有代码),
  4. 我使用了一个后台工作线程,该线程将加载数据,完成后将通知应用程序它已完成其工作。
  5. 现在真正的问题来了。 所有 UI(主窗体及其子用户控件)都是在主主线程上创建的。 在用户控件的 LOAD 方法中,我根据用户控件上某些控件(如文本框)的值获取数据。

伪代码如下所示:

CODE 1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

它给出的异常是

跨线程操作无效:从创建它的线程以外的线程访问控制。

为了了解更多信息,我进行了一些谷歌搜索,并提出了一个建议,例如使用以下代码

代码 2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it won't give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

但似乎我仍然回到了第一个方向。 再次申请 变得没有反应。 这似乎是由于执行了第 1 行 if 条件所致。 加载任务再次由父线程完成,而不是我生成的第三个线程。

我不知道我的看法是对还是错。

我该如何解决这个问题以及执行 Line#1 if 块的效果是什么?

情况是这样的:我想根据控件的值将数据加载到全局变量中。 我不想从子线程更改控件的值。 我不会从子线程中执行此操作。

所以只有访问该值,才能从数据库中获取相应的数据。

I have a scenario. (Windows Forms, C#, .NET)

  1. There is a main form which hosts some user control.
  2. The user control does some heavy data operation, such that if I directly call the UserControl_Load method the UI become nonresponsive for the duration for load method execution.
  3. To overcome this I load data on different thread (trying to change existing code as little as I can)
  4. I used a background worker thread which will be loading the data and when done will notify the application that it has done its work.
  5. Now came a real problem. All the UI (main form and its child usercontrols) was created on the primary main thread. In the LOAD method of the usercontrol I'm fetching data based on the values of some control (like textbox) on userControl.

The pseudocode would look like this:

CODE 1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

The Exception it gave was

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

To know more about this I did some googling and a suggestion came up like using the following code

CODE 2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it won't give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

But it still seems that I've come back to square one. The Application again
becomes unresponsive. It seems to be due to the execution of line #1 if condition. The loading task is again done by the parent thread and not the third that I spawned.

I don't know whether I perceived this right or wrong.

How do I resolve this and also what is the effect of execution of Line#1 if block?

The situation is this: I want to load data into a global variable based on the value of a control. I don't want to change the value of a control from the child thread. I'm not going to do it ever from a child thread.

So only accessing the value so that the corresponding data can be fetched from the database.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(22

§对你不离不弃 2024-07-14 00:11:03

跨线程操作有两种选择。

Control.InvokeRequired Property 

第二个是使用

SynchronizationContext Post Method

Control.InvokeRequired 仅当工作从 Control 类继承的控件时才有用,而 SynchronizationContext 可以在任何地方使用。 一些有用的信息如下链接

跨线程更新 UI | .Net

跨线程更新 UI使用 SynchronizationContext | .Net

There are two options for cross thread operations.

Control.InvokeRequired Property 

and second one is to use

SynchronizationContext Post Method

Control.InvokeRequired is only useful when working controls inherited from Control class while SynchronizationContext can be used anywhere. Some useful information is as following links

Cross Thread Update UI | .Net

Cross Thread Update UI using SynchronizationContext | .Net

江南月 2024-07-14 00:11:02

行动 y; //在类内声明

label1.Invoke(y=()=>label1.Text="text");

Action y; //declared inside class

label1.Invoke(y=()=>label1.Text="text");

遮了一弯 2024-07-14 00:10:59

只需使用这个:

this.Invoke((MethodInvoker)delegate
            {
                YourControl.Property= value; // runs thread safe
            });

Simply use this:

this.Invoke((MethodInvoker)delegate
            {
                YourControl.Property= value; // runs thread safe
            });
洋洋洒洒 2024-07-14 00:10:57

同样的问题:how-to-update-the-gui -from-another-thread-in-c

两种方式:

  1. 在 e.result 中返回值并使用它在 backgroundWorker_RunWorkerCompleted 事件中设置 yout 文本框值

  2. 声明一些变量以在单独的类中保存这些类型的值(它将作为数据持有者)。 创建此类的静态实例,然后您可以通过任何线程访问它。

例子:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}

Same question : how-to-update-the-gui-from-another-thread-in-c

Two Ways:

  1. Return value in e.result and use it to set yout textbox value in backgroundWorker_RunWorkerCompleted event

  2. Declare some variable to hold these kind of values in a separate class (which will work as data holder) . Create static instance of this class adn you can access it over any thread.

Example:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}
李白 2024-07-14 00:10:57
this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));
this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));
国产ˉ祖宗 2024-07-14 00:10:55

例如,从 UI 线程的 Control 获取文本:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
        text = CStr(ctl.Invoke(
            New GetControlTextInvoker(AddressOf GetControlText), ctl))
    Else
        text = ctl.Text
    End If

    Return text
End Function

For example to get the text from a Control of the UI thread:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
        text = CStr(ctl.Invoke(
            New GetControlTextInvoker(AddressOf GetControlText), ctl))
    Else
        text = ctl.Text
    End If

    Return text
End Function
一指流沙 2024-07-14 00:10:54

与之前的答案相同,
但一个非常简短的补充,允许使用所有 Control 属性而不会出现跨线程调用异常。

帮助方法

/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
    if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
    {
        a();
    }));
    else return false;

    return true;
}

示例用法

// usage on textbox
public void UpdateTextBox1(String text)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
    textBox1.Text = ellapsed;
}

//Or any control
public void UpdateControl(Color c, String s)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
    myControl.Text = s;
    myControl.BackColor = c;
}

Along the same lines as previous answers,
but a very short addition that Allows to use all Control properties without having cross thread invokation exception.

Helper Method

/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
    if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
    {
        a();
    }));
    else return false;

    return true;
}

Sample Usage

// usage on textbox
public void UpdateTextBox1(String text)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
    textBox1.Text = ellapsed;
}

//Or any control
public void UpdateControl(Color c, String s)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
    myControl.Text = s;
    myControl.BackColor = c;
}
且行且努力 2024-07-14 00:10:53

我在 xamarin studio 之外的 Visual Studio winforms 原型项目中编写 iOS 手机单点触控应用程序控制器时发现需要这样做。 我更喜欢尽可能在 VS 中编程而不是在 xamarin studio 中编程,我希望控制器与手机框架完全解耦。 通过这种方式,为 Android 和 Windows Phone 等其他框架实现这一点对于将来的使用来说会更加容易。

我想要一个解决方案,其中 GUI 可以响应事件,而无需处理每次按钮单击背后的跨线程切换代码。 基本上让类控制器处理它以保持客户端代码简单。 您可能在 GUI 上有许多事件,如果您可以在类中的一个地方处理它会更干净。 我不是多主题专家,如果这有缺陷,请告诉我。

public partial class Form1 : Form
{
    private ExampleController.MyController controller;

    public Form1()
    {          
        InitializeComponent();
        controller = new ExampleController.MyController((ISynchronizeInvoke) this);
        controller.Finished += controller_Finished;
    }

    void controller_Finished(string returnValue)
    {
        label1.Text = returnValue; 
    }

    private void button1_Click(object sender, EventArgs e)
    {
        controller.SubmitTask("Do It");
    }
}

GUI 形式不知道控制器正在运行异步任务。

public delegate void FinishedTasksHandler(string returnValue);

public class MyController
{
    private ISynchronizeInvoke _syn; 
    public MyController(ISynchronizeInvoke syn) {  _syn = syn; } 
    public event FinishedTasksHandler Finished; 

    public void SubmitTask(string someValue)
    {
        System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
    }

    private void submitTask(string someValue)
    {
        someValue = someValue + " " + DateTime.Now.ToString();
        System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.

        if (Finished != null)
        {
            if (_syn.InvokeRequired)
            {
                _syn.Invoke(Finished, new object[] { someValue });
            }
            else
            {
                Finished(someValue);
            }
        }
    }
}

I found a need for this while programming an iOS-Phone monotouch app controller in a visual studio winforms prototype project outside of xamarin stuidio. Preferring to program in VS over xamarin studio as much as possible, I wanted the controller to be completely decoupled from the phone framework. This way implementing this for other frameworks like Android and Windows Phone would be much easier for future uses.

I wanted a solution where the GUI could respond to events without the burden of dealing with the cross threading switching code behind every button click. Basically let the class controller handle that to keep the client code simple. You could possibly have many events on the GUI where as if you could handle it in one place in the class would be cleaner. I am not a multi theading expert, let me know if this is flawed.

public partial class Form1 : Form
{
    private ExampleController.MyController controller;

    public Form1()
    {          
        InitializeComponent();
        controller = new ExampleController.MyController((ISynchronizeInvoke) this);
        controller.Finished += controller_Finished;
    }

    void controller_Finished(string returnValue)
    {
        label1.Text = returnValue; 
    }

    private void button1_Click(object sender, EventArgs e)
    {
        controller.SubmitTask("Do It");
    }
}

The GUI form is unaware the controller is running asynchronous tasks.

public delegate void FinishedTasksHandler(string returnValue);

public class MyController
{
    private ISynchronizeInvoke _syn; 
    public MyController(ISynchronizeInvoke syn) {  _syn = syn; } 
    public event FinishedTasksHandler Finished; 

    public void SubmitTask(string someValue)
    {
        System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
    }

    private void submitTask(string someValue)
    {
        someValue = someValue + " " + DateTime.Now.ToString();
        System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.

        if (Finished != null)
        {
            if (_syn.InvokeRequired)
            {
                _syn.Invoke(Finished, new object[] { someValue });
            }
            else
            {
                Finished(someValue);
            }
        }
    }
}
余罪 2024-07-14 00:10:52

如果您正在使用的对象没有,这是一种替代方法

(InvokeRequired)

如果您正在使用除主窗体之外的类中的主窗体以及主窗体中的对象但没有InvokeRequired

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

它的工作方式与上面相同,但如果您没有带有 invokerequired 的对象,但可以访问 MainForm,则这是一种不同的方法

Here is an alternative way if the object you are working with doesn't have

(InvokeRequired)

This is useful if you are working with the main form in a class other than the main form with an object that is in the main form, but doesn't have InvokeRequired

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

It works the same as above, but it is a different approach if you don't have an object with invokerequired, but do have access to the MainForm

又怨 2024-07-14 00:10:50

解决此问题的简单且可重用的方法。

扩展方法

public static class FormExts
{
    public static void LoadOnUI(this Form frm, Action action)
    {
        if (frm.InvokeRequired) frm.Invoke(action);
        else action.Invoke();
    }
}

示例用法

private void OnAnyEvent(object sender, EventArgs args)
{
    this.LoadOnUI(() =>
    {
        label1.Text = "";
        button1.Text = "";
    });
}

Simple and re-usable way to work around this problem.

Extension Method

public static class FormExts
{
    public static void LoadOnUI(this Form frm, Action action)
    {
        if (frm.InvokeRequired) frm.Invoke(action);
        else action.Invoke();
    }
}

Sample Usage

private void OnAnyEvent(object sender, EventArgs args)
{
    this.LoadOnUI(() =>
    {
        label1.Text = "";
        button1.Text = "";
    });
}
み零 2024-07-14 00:10:49

您需要查看Backgroundworker 示例:
http://msdn.microsoft.com/en-us/library /system.componentmodel.backgroundworker.aspx
特别是它如何与 UI 层交互。 根据您的帖子,这似乎可以回答您的问题。

You need to look at the Backgroundworker example:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Especially how it interacts with the UI layer. Based on your posting, this seems to answer your issues.

梦开始←不甜 2024-07-14 00:10:49

使用异步/等待和回调的新外观。 如果您将扩展方法保留在项目中,则只需要一行代码。

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

您可以向扩展方法添加其他内容,例如将其包装在 Try/Catch 语句中,允许调用者告诉它完成后返回什么类型,向调用者提供异常回调:

添加 Try Catch、自动异常记录和回调

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name="T">The type to return</typeparam>
    /// <param name="Code">The callback to the code</param>
    /// <param name="Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }

A new look using Async/Await and callbacks. You only need one line of code if you keep the extension method in your project.

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

You can add other things to the Extension method such as wrapping it in a Try/Catch statement, allowing caller to tell it what type to return after completion, an exception callback to caller:

Adding Try Catch, Auto Exception Logging and CallBack

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name="T">The type to return</typeparam>
    /// <param name="Code">The callback to the code</param>
    /// <param name="Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }
做个少女永远怀春 2024-07-14 00:10:49

这不是解决此错误的推荐方法,但您可以快速抑制它,它会完成工作。 我更喜欢用它来制作原型或演示。 添加

CheckForIllegalCrossThreadCalls = false

Form1() 构造函数中。

This is not the recommended way to solve this error but you can suppress it quickly, it will do the job . I prefer this for prototypes or demos . add

CheckForIllegalCrossThreadCalls = false

in Form1() constructor .

罪歌 2024-07-14 00:10:48

按照最简单的(在我看来)方法从另一个线程修改对象:

using System.Threading.Tasks;
using System.Threading;

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}

Follow the simplest (in my opinion) way to modify objects from another thread:

using System.Threading.Tasks;
using System.Threading;

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}
夢归不見 2024-07-14 00:10:47

UI 跨线程问题最干净(且正确)的解决方案是使用 SynchronizationContext,请参阅 在多线程应用程序中同步调用 UI 一文,它解释得非常好。

The cleanest (and proper) solution for UI cross-threading issues is to use SynchronizationContext, see Synchronizing calls to the UI in a multi-threaded application article, it explains it very nicely.

最丧也最甜 2024-07-14 00:10:47

我发现需要在与表单相关的所有方法中散布的检查和调用代码过于冗长且不必要。 这是一个简单的扩展方法,可以让您完全摆脱它:

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) 
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

然后您可以简单地执行以下操作:

textbox1.Invoke(t => t.Text = "A");

不再乱搞 - 简单。

I find the check-and-invoke code which needs to be littered within all methods related to forms to be way too verbose and unneeded. Here's a simple extension method which lets you do away with it completely:

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) 
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

And then you can simply do this:

textbox1.Invoke(t => t.Text = "A");

No more messing around - simple.

傲性难收 2024-07-14 00:10:47

.NET 中的控件通常不是线程安全的。 这意味着您不应该从该控件所在线程以外的线程访问该控件。 为了解决这个问题,您需要调用该控件,这正是您的第二个示例所尝试的。

但是,在您的情况下,您所做的只是将长时间运行的方法传递回主线程。 当然,这并不是您真正想要做的。 您需要重新考虑一下这一点,以便您在主线程上所做的只是在这里或那里设置一个快速属性。

Controls in .NET are not generally thread safe. That means you shouldn't access a control from a thread other than the one where it lives. To get around this, you need to invoke the control, which is what your 2nd sample is attempting.

However, in your case all you've done is pass the long-running method back to the main thread. Of course, that's not really what you want to do. You need to rethink this a little so that all you're doing on the main thread is setting a quick property here and there.

痞味浪人 2024-07-14 00:10:46

我在使用 FileSystemWatcher 时遇到了这个问题,并发现以下代码解决了该问题:

fsw.SynchronizingObject = this

然后控件使用当前表单对象来处理事件,因此将位于同一线程上。

I have had this problem with the FileSystemWatcher and found that the following code solved the problem:

fsw.SynchronizingObject = this

The control then uses the current form object to deal with the events, and will therefore be on the same thread.

铁轨上的流浪者 2024-07-14 00:10:46

根据 Prera​​k K 的更新评论(已删除):

我想我没有正确提出问题。

情况是这样的:我想根据控件的值将数据加载到全局变量中。 我不想从子线程更改控件的值。 我不会从子线程中执行此操作。

因此只有访问该值,才能从数据库中获取相应的数据。

您想要的解决方案应该如下所示:

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

在尝试切换回控件的线程之前,在单独的线程中进行认真的处理。 例如:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}

As per Prerak K's update comment (since deleted):

I guess I have not presented the question properly.

Situation is this: I want to load data into a global variable based on the value of a control. I don't want to change the value of a control from the child thread. I'm not going to do it ever from a child thread.

So only accessing the value so that corresponding data can be fetched from the database.

The solution you want then should look like:

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

Do your serious processing in the separate thread before you attempt to switch back to the control's thread. For example:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}
甩你一脸翔 2024-07-14 00:10:46

UI 中的线程模型

请阅读线程模型< /a> 在 UI 应用程序中(旧 VB 链接位于此处)以理解基本概念。 该链接导航到描述 WPF 线程模型的页面。 然而,Windows 窗体采用了相同的想法。

UI 线程

在此处输入图像描述

在此处输入图像描述

BeginInvoke 和 Invoke 方法

在此处输入图像描述

调用

在此处输入图像描述

BeginInvoke

在此处输入图像描述

代码解决方案

阅读问题的答案 如何从 C# 中的另一个线程更新 GUI?
对于 C# 5.0 和 .NET 4.5,推荐的解决方案位于此处

Threading Model in UI

Please read the Threading Model in UI applications (old VB link is here) in order to understand basic concepts. The link navigates to page that describes the WPF threading model. However, Windows Forms utilizes the same idea.

The UI Thread

  • There is only one thread (UI thread), that is allowed to access System.Windows.Forms.Control and its subclasses members.
  • Attempt to access member of System.Windows.Forms.Control from different thread than UI thread will cause cross-thread exception.
  • Since there is only one thread, all UI operations are queued as work items into that thread:

enter image description here

enter image description here

BeginInvoke and Invoke methods

enter image description here

Invoke

enter image description here

BeginInvoke

enter image description here

Code solution

Read answers on question How to update the GUI from another thread in C#?.
For C# 5.0 and .NET 4.5 the recommended solution is here.

稀香 2024-07-14 00:10:46

您只想使用 InvokeBeginInvoke 来完成更改 UI 所需的最少工作。 您的“重”方法应该在另一个线程上执行(例如通过BackgroundWorker),然后使用Control.Invoke/Control.BeginInvoke只是为了更新用户界面。 这样你的 UI 线程就可以自由地处理 UI 事件等。

请参阅我的 线程文章 了解更多信息WinForms 示例 - 尽管本文是在 BackgroundWorker 到了现场,这方面恐怕还没有更新。 BackgroundWorker 只是稍微简化了回调。

You only want to use Invoke or BeginInvoke for the bare minimum piece of work required to change the UI. Your "heavy" method should execute on another thread (e.g. via BackgroundWorker) but then using Control.Invoke/Control.BeginInvoke just to update the UI. That way your UI thread will be free to handle UI events etc.

See my threading article for a WinForms example - although the article was written before BackgroundWorker arrived on the scene, and I'm afraid I haven't updated it in that respect. BackgroundWorker merely simplifies the callback a bit.

此岸叶落 2024-07-14 00:10:46

我知道现在已经太晚了。 然而,即使在今天,您是否在访问跨线程控件时遇到困难? 这是迄今为止最短的答案:P

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

这就是我从线程访问任何表单控件的方式。

I know its too late now. However even today if you are having trouble accessing cross thread controls? This is the shortest answer till date :P

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

This is how i access any form control from a thread.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文