我有一个设想。(Windows Forms, c#, .NET)

There is a main form which hosts some user control. 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. To overcome this I load data on different thread (trying to change existing code as little as I can) 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. 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.

伪代码看起来像这样:

代码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条件的执行。加载任务再次由父线程完成,而不是由我生成的第三个线程完成。

我不知道我认为这是对还是错。

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

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

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


当前回答

简单和可重用的方法来解决这个问题。

扩展方法

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

其他回答

同样的问题:如何从c中的另一个线程更新gui

两种方式:

在e.result中返回值,并使用它来设置backgroundWorker_RunWorkerCompleted事件的文本框值 在一个单独的类中声明一些变量来保存这些类型的值(它将作为数据持有者)。创建该类的静态实例,您可以在任何线程上访问它。

例子:

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

UI跨线程问题最干净(也是最合适)的解决方案是使用SynchronizationContext,请参见多线程应用程序中同步对UI的调用的文章,它很好地解释了这一点。

根据Prerak 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;
        }
    }
}

跨线程操作有两个选项。

Control.InvokeRequired Property 

第二个是使用

SynchronizationContext Post Method

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

跨线程更新UI | .Net

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

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

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

你可以在Extension方法中添加其他东西,比如用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
        }
    }