在使用委托运行后台工作时如何处理控件启用/禁用选项?

时间:2019-02-19 15:23:10

标签: c# .net delegates

在button_click事件上,我有一个查询,该查询将花费很长时间。所以我在BackgroundWorker上运行它

     private void btnGenerate_Click(object sender, EventArgs e)
        {
            btnGenerate.Enabled = false;
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += delegate (object s, DoWorkEventArgs args)
            {
                Data = DataLoader.GetData(Environment.UserName); // stored procedure execution 
                if (Data != null)
                {
                    GenerateExcel(Data);
                    GenerateSingleExcel(Data);
                } 
            };    
            worker.RunWorkerCompleted += delegate (object s, RunWorkerCompletedEventArgs args)
            {
                progressBar1.Visible = false;// ProgressBarStyle.Marquee 
                btnGenerate.Enabled = true;   
            };    
            worker.RunWorkerAsync();
}

我的问题是,我需要设置

  

btnGenerate.Enable = false;

在button_click上按

。并在执行完成后启用。

我在RunWorkerCompleted内尝试过,但显示

  

'跨线程操作无效:控制'btnGenerate'是从创建该线程的线程之外的线程访问的。'

任何建议都会有所帮助。

1 个答案:

答案 0 :(得分:1)

您的主要问题是BackgroundWorker的事件是在工作线程上执行的,而不是在UI线程上执行的。但是UI元素只能从UI线程访问。

为解决此问题,我建议使用async/await代替BackgroundWorker

// declare as async
private async void btnGenerate_Click(object sender, EventArgs e)
{
    btnGenerate.Enabled = false;
    Data = await Task.Run(() => {
                var data = DataLoader.GetData(Environment.UserName); // stored procedure execution 
                if (data != null)
                {
                    GenerateExcel(Data);
                    GenerateSingleExcel(Data);
                }
                return data; // as suggested by Vlad, don't set Data on this thread
            });    

    // this is now executed back on the UI thread
    progressBar1.Visible = false;// ProgressBarStyle.Marquee 
    btnGenerate.Enabled = true;   
}

如果DataLoader提供了一个异步GetDataAsync,那将是更好的选择,因此您将不需要Task.Run()


如果由于某种原因无法使用async,则您的RunWorkerCompleted处理程序应使用InvokeBeginInvoke

worker.RunWorkerCompleted += OnRunWorkerCompleted;
//...
public void OnRunWorkerCompleted(object s, RunWorkerCompletedEventArgs args)
{
    if (InvokeRequired)
    {
        // not on the UI thread - use (Begin-)Invoke
        BeginInvoke(new RunWorkerCompletedEventHandler(OnRunWorkerCompleted), s, args);
        return;
    }

    // now we're on the UI thread
    progressBar1.Visible = false;// ProgressBarStyle.Marquee 
    btnGenerate.Enabled = true;   
}