【问题标题】:Cross thread operation not valid in BackgroundWorker跨线程操作在 BackgroundWorker 中无效
【发布时间】:2015-12-13 02:08:06
【问题描述】:

我想在数据网格视图中显示一些有关表单加载的数据,我想显示的数据是大量行,当我使用后台工作处理器时,它会显示以下错误。

我的代码:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    try
    {
        FTPUtility obj = new FTPUtility();
        dataGridViewRequest.DataSource = obj.ListRequestFiles();
        dataGridViewRequest.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
        dataGridViewRequest.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
        dataGridViewRequest.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;

        dataGridViewResponses.DataSource = obj.ListResponseFiles();
        dataGridViewResponses.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
        dataGridViewResponses.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
        dataGridViewResponses.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;

    }
    catch (Exception ex)
    {

        MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

表单加载:

private void FormFTP_Load(object sender, EventArgs e)
{
    try
    {

        //this.comboBoxRequests.SelectedIndex = 0;
        backgroundWorker1.RunWorkerAsync();

    }
    catch (Exception ex)
    {

        MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

【问题讨论】:

  • 您正在做的事情(= 试图从 BackgroundWorker 线程修改 GUI 元素)无法完成。您最好阅读有关 C#/.NET 中的 BackgroundWorker/multithreading 的内容,因为您的想法还不清楚。
  • @varocarbas 我正在尝试从本地 Windows 应用程序 datagridview 上的 ftp 获取文件,当我加载数据时它需要太多时间,所以我想先加载主窗体,然后在几秒钟后文件显示在 datagridview 中。
  • 正如我之前的评论中所解释的:您的代码正在做的事情证明您绝对缺乏关于BackgroundWorker 和基本思想(即多线程)的知识。提高你在这两个方面的知识,而不是盲目测试并抱怨你看到的每一个问题。
  • @varocarbas 我解决了问题,谢谢

标签: winforms c#-4.0 backgroundworker


【解决方案1】:

有许多不同的方法可以防止表单被冻结。
例如,您可以像这样加载数据:

private async void Form_Load(object sender, EventArgs e)
{
    //do some initializations
    await LoadData();
    //do some other initializations that you need to perform.
}

private async Task LoadData()
{
    //Load your data here
    //For example
    FTPUtility obj = new FTPUtility();
    dataGridViewRequest.DataSource = obj.ListRequestFiles();
}

这样在运行表单时,命令在 UI 响应时按照您编写的顺序运行,您将不会遇到使用BackgroundWorker 或跨线程操作异常等线程的常见困难。

关键在于使用 async/await。更多信息请阅读Asynchronous Programming with Async and Await

记住这种方式,每一个你想调用LoadData的地方,都应该这样调用:

await LoadData();

你写这段代码的方法应该是异步的:

private async void RefreshButton_Click(object sender, EventArgs e)
{
    await LoadData();
}

为 .Net 4.0 编辑


对于 .Net 4.0,您可以同时使用 Task.RunBackgroundWorker。我推荐Task.Run,因为它更简单,更易读。

请注意,当您从 UI 以外的其他线程访问 UI 元素时,将引发跨线程操作异常。在这种情况下,您应该改用this.Invoke(new Action(()=>{/*Access UI Here*/}));。并且永远不要在你的调用部分中放置一个耗时的任务。

BackgroundWorker 方法:

private void Form1_Load(object sender, EventArgs e)
{
    backgroundWorker1.RunWorkerAsync();
    //If you put some code here for example MessageBox.Show(""); 
    //The code will immadiately run and don't wait for worker to complete the task.
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    this.LoadData();
}

public void LoadData()
{
    var ftp = new FtpUtility();
    var data = ftp.ListRequestFiles();
    this.Invoke(new Action(() =>
    {
        //Setup columns and other UI stuff
        //Set datasource of grid
        this.dataGridView1.DataSource = data;
    }));
}
  • 记住你以前使用LoadData的所有地方,现在你应该改用backgroundWorker1.RunWorkerAsync();
  • 如果你想在工人完成任务后做一份工作,请将你的工作放在backgroundWorker1_RunWorkerCompletedInvokeLoadData 的一部分。

Task.Run 方法

private void Form1_Load(object sender, EventArgs e)
{
    Task.Run(() =>
    {
        LoadData();
    })
    .ContinueWith(x =>
    {
        //You can put codes you want run after LoadData completed here
        //If you access the UI here, you should use Invoke
    });
    //If you put some code here for example MessageBox.Show(""); 
    //The code will immadiately run and don't wait for worker to complete the task.
}

public void LoadData()
{
    var ftp = new FtpUtility();
    var data = ftp.ListRequestFiles();
    this.Invoke(new Action(() =>
    {
        //Setup columns and other UI stuff
        //Set datasource of grid
        this.dataGridView1.DataSource = data;
    }));
}
  • 记住你以前使用LoadData的所有地方,现在你应该改用Task.Run(()=>{LoadData();});
  • 如果您想在LoadData 完成后做一份工作,请将您的工作放入ContinueWithInvokeLoadData 的一部分。

【讨论】:

  • 私有异步它给出一个错误,你是否缺少命名空间。
  • @RazimKhan 它适用于 .Net 4.5,看来您正在使用 .Net 4.0,所以,我将更新 .Net 4.0 的代码
  • @RazimKhan 我编辑了答案并提供了两种方法,希望对您有所帮助:)
  • @RazimKhan,我认为这个答案对未来的读者会有帮助。你最后用了什么解决方案?
  • @RazimKhan 干得好,你为 UI 冻结问题做了什么?
【解决方案2】:

试试这个。

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    try
    {

       dataGridViewRequest.Invoke(new Action(() => {
            FTPUtility obj = new FTPUtility();
            dataGridViewRequest.DataSource = obj.ListRequestFiles();
            dataGridViewRequest.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            dataGridViewRequest.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            dataGridViewRequest.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;

            dataGridViewResponses.DataSource = obj.ListResponseFiles();
            dataGridViewResponses.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            dataGridViewResponses.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            dataGridViewResponses.Columns[2].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
       }));
    }
    catch (Exception ex)
    {

        MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

private void FormFTP_Load(object sender, EventArgs e)
{
    try
    {

        //this.comboBoxRequests.SelectedIndex = 0;
        backgroundWorker1.RunWorkerAsync();

    }
    catch (Exception ex)
    {

        MessageBox.Show(ex.Message, "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

【讨论】:

  • 永远不要尝试;)您将耗时的任务放在Invoke 中,这意味着它将在 UI 线程中运行,并且会再次冻结 UI。相反,您应该先运行耗时的任务,然后在调用部分,仅访问 UI 并设置 UI 属性,在此示例中,obj.ListRequestFiles(); 应在调用之前调用,而不是在调用部分。要自己测试,使用Thread.Sleep(5000) 而不是obj.ListRequestFiles(); 即可查看结果。
  • 它不会冻结 UI,因为您使用的是 backgroundworker :)
  • 关于 BackgroundWorker 的常见错误,是的,它在不同的线程上运行,但是当您使用 Invoke 时,传递给调用的代码将在 UI 线程中运行,因为您将主在该动作中冻结一行代码,UI 将被冻结。要自己测试,使用Thread.Sleep(5000) 而不是obj.ListRequestFiles(); 即可查看结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-08
  • 2012-09-12
  • 1970-01-01
  • 1970-01-01
  • 2011-07-11
相关资源
最近更新 更多