【问题标题】:Update C# datagridviews in separate thread在单独的线程中更新 C# datagridviews
【发布时间】:2015-02-17 23:15:23
【问题描述】:

我有一个带有 datagridview 和一些按钮的 windows 窗体。单击其中一个按钮将调用名为loadMyData() 的方法,该方法从 csv 读取一些数据并将它们放入表单中的三个数据网格视图中。

代码是这样的:

public partial class NewForm : Form
{

    private void loadData_Click_1(object sender, EventArgs e) // load market data, create a base copy and update gridview
    {
        ThreadStart thread1Start = new ThreadStart(loadMyData);
        Thread t1 = new Thread(thread1Start);
        t1.Start();          
    }

    public void loadMyData()
    {
    dataMap = dataLoader.newLoadTheData(dataMap, grid1, grid2) 


    }
}

dataLoader.newLoadTheData 是一个静态方法,它将我的 datagridviews (grid1, grid2) 和一个字典 (dataMap) 作为输入。该方法只是从 csv 中读取一些数据并将数字放入 2 个数据网格视图中。这些是从此方法更新的,并且该方法还返回更新的字典(dataMap)。当方法loadMyData() 正常执行时一切正常,但是当我将其作为线程执行时出现此错误:

跨线程操作无效:控件“grid1”从 线程不是创建它的线程。

我意识到我可能会使用“调用”之类的东西,但我真的找不到一个明确的例子来说明如何在我的情况下执行此操作。任何人都可以帮助解决tjis的情况吗?我应该如何更改代码以使其正常工作?

【问题讨论】:

    标签: c# multithreading threadpool


    【解决方案1】:

    从另一个线程处理您的网格时,您应该执行以下操作:

    if (grid1.InvokeRequired)
       grid1.Invoke(new Action(() => { /*do my stuff here*/ })
    else
    {
       /*do my stuff here*/
    }
    

    【讨论】:

    • 谢谢。网格由静态方法dataLoader.newLoadTheData 更新,并且只是为在 csv 中找到的每个值添加一行(最后,csv 中的所有非空单元格都将在 gridview 中)。那么我应该将代码Invoke 放在方法dataLoader.newLoadTheData 中吗?
    • 所以每次我对线程中的网格做一些事情时,我都需要使用grid.Invoke(new Action(() => { /* read from csv and update gridview adding one row*/ })?基本上在else 部分(所以当! grid.InvokeRequired 时)我仍然需要放置从csv 读取的相同代码并填充gridview 以便它在不在多线程中运行时仍然可以运行?这种代码重复有必要吗?
    • 您可以将该代码放在一个可以从两个地方调用的函数中。基本上,如果允许您根据 InvokeRequired 属性采取不同的操作。在你的情况下,你想做同样的事情,只是在不同的线程上。
    • 所以我可以保留 csv 读取部分原样(因为那里不涉及网格),但是在 dataLoader.newLoadTheData 方法中创建另一个函数并将网格作为输入传递。然后新功能将进入Invoke,例如:if (grid.InvokeRequired) grid.Invoke(new Action(() => { newFunct(grid1,grid2)}) else { newFunct(grid1,grid2) }
    • 任何地方都可以,只要它在应该运行的时候运行。我会把它放在 newLoadTheData(或另一个由 newLoadTheData 调用的方法)中,因为这是从你生成的不同线程中调用的方法。
    【解决方案2】:

    System.Threading.Tasks 允许您轻松创建子任务并在所有完成后运行完成块。如果你指定 UI 上下文,那么完成块将在 UI 线程中运行,不需要Invoke()

    代码:

            var ui = TaskScheduler.FromCurrentSynchronizationContext();
            Task.Factory.StartNew(
                                  () =>
                                  {
                                      // this runs in worker thread
                                      loadMyData(); 
                                      DoSomeLengthyJob();
                                      DoSomethingElse();
                                  }) 
                .ContinueWith(t =>
                              {
                                  // now we are in UI thread
                                  // now update the UI with whatever you want
                                  // with the results from your worker thread
    
                                  dataGridView1.Rows.Add();
                              }, ui);
    

    告诉我更多

    【讨论】:

    • 但是这样我就不能并行运行了?
    • @mickG 不正确。当上面调用loadMyData() 时,它会在单独的线程中运行
    • 只是我看到了.ContinueWith(),所以如果我有其他方法(例如更新网格),那么它将在loadMyData() 之后运行。我错了吗?
    • @mickG .ContinueWith() 最后运行,这是正确的。在该块中,使用工作线程的结果更新 UI。我更新了我的示例以显示您可以在工作线程块中执行的其他操作。
    【解决方案3】:

    您必须将调用编组回 UI 线程。

    您使用的是 WinForms 还是 WPF?

    在 WPF 中,您可以使用 Dispatcher。

    在 WinForms 中:

    试试

    // Get the UI thread's context in the constructor.
    var context = TaskScheduler.FromCurrentSynchronizationContext();
    
    
            // Then its possible to start a task directly on the UI thread
            var token = Task.Factory.CancellationToken;
            Task.Factory.StartNew(() =>
            {
                this.label1.Text = "Task past first work section...";
            }, token, TaskCreationOptions.None, context);
    

    编辑:

    您收到此错误的原因是您正试图从另一个线程访问 Grid 控件。通常,大多数 UI 应用程序都处于 STA(单线程关联)模型中,其中与 UI 的任何交互都必须在“UI”线程上完成,该线程通常是应用程序启动的主线程/第一个线程。

    当您在 background 线程上加载数据时,完成后,您需要一种方法来 Marshal(调用/运行)更新 Grid 的代码在 Main/UI 线程上。

    要实现这一点,您可以使用其当前 SynchronizationContext 在主线程上创建一个 TaskScheduler(因为在窗口/控件的构造函数中,当前上下文将是 UI 线程),然后您可以将该上下文传递给 Task .Factory.StartNew 方法作为参数,以便它知道“编组”(调用/运行)给定“上下文”上的代码,即 UI 线程

    【讨论】:

    • 谢谢。它是一个 WinForm。你能解释一下这是如何工作的吗?
    猜你喜欢
    • 2011-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多