如何在Winform中使用多线程?

时间:2011-06-16 09:37:49

标签: c# .net winforms multithreading

我是多线程的新手。我有一个带有标签和进度条的winform。

我想显示处理结果。首先,我使用Application.DoEvents()方法。但是,我发现表格很冷。

然后我在MSDN上阅读了一些关于多线程的文章。

其次,我使用BackgroundWorker来完成它。

this.bwForm.DoWork += (o, arg) => { DoConvert(); };
this.bwForm.RunWorkerAsync();

表格不会冻结,我可以在处理时拖动/ drog。不幸的是,它会抛出InvalidOperationException。所以我必须使用它。 Control.CheckForIllegalCrossThreadCalls = false;我确保这不是最终的解决方案。

你有什么建议吗,专家?

修改 当我调用listview时抛出InvalidOperationException。此代码位于DoWork()。

          foreach (ListViewItem item in this.listView1.Items)
            {
                //........some operation
                 lbFilesCount.Text = string.Format("{0} files", listView1.Items.Count);
                 progressBar1.Value++;
            }

EDIT2: 我在DoWork()中使用委托并调用它并不会抛出异常。但形式再次冻结。怎么做才能使表格在处理时可以放置/悬垂?

9 个答案:

答案 0 :(得分:9)

调用UI线程。例如:

void SetControlText(Control control, string text)
{
    if (control.InvokeRequired)
        control.Invoke(SetControlText(control, text));
    else
        control.Text = text;
}

答案 1 :(得分:9)

您只能从UI线程(WinForm one)设置进度条用户控件属性。 使用BackgroundWorker最简单的方法是使用ProgressChanged事件:

private BackgroundWorker bwForm;
private ProgressBar progressBar;

在WinForm构造函数中:

this.progressBar = new ProgressBar();
this.progressBar.Maximum = 100;
this.bwForm = new BackgroundWorker();
this.bwForm.DoWork += new DoWorkEventHandler(this.BwForm_DoWork);
this.bwForm.ProgressChanged += new ProgressChangedEventHandler(this.BwForm_ProgressChanged);
this.bwForm.RunWorkerAsync();

...

void BwForm_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker bgw = sender as BackgroundWorker;
    // Your DoConvert code here
    // ...          
    int percent = 0;
    bgw.ReportProgress(percent);
    // ...
}

void BwForm_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    this.progressBar.Value = e.ProgressPercentage;
}

见这里:http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

修改

所以我不理解你的问题,我认为这是关于在工作期间显示进度条的进展。 如果是关于工作的结果,请在BwForm_DoWork事件中使用e.Result。 为已完成的事件添加新的事件处理程序并管理结果:

this.bwForm.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.BwForm_RunWorkerCompleted);

...

private void BwForm_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    YourResultStruct result = e.Result as YourResultStruct;
    if (e.Error != null && result != null)
    {
        // Handle result here
    }
}

答案 2 :(得分:6)

避免致电Application.DoEvents。通常情况下,这会导致比解决的问题更多的问题。它通常被认为是不好的做法,因为存在更好的替代方案,可以保持用户界面的消息。

避免更改设置CheckForIllegalCrossThreadCalls = false。这不能解决任何问题。它只掩盖了问题。问题是您正在尝试从主UI线程以外的线程访问UI元素。如果您禁用CheckForIllegalCrossThreadCalls,那么您将不再获得异常,而是您的应用程序将以不可预测和惊人的方式失败。

DoWork事件处理程序中,您需要定期调用ReportProgress。从您的Form开始,您需要订阅ProgressChanged个活动。从ProgressChanged事件处理程序中访问UI元素是安全的,因为它会自动封送到UI线程中。

答案 3 :(得分:5)

您应该使用Control.Invoke()。有关详细信息,另请参阅this question

答案 4 :(得分:5)

最干净的方法就是使用BackGroundWorker

你错过了一些观点:

  1. 您无法访问DoWork事件处理程序中的表单元素,因为这会导致跨线程方法调用,这应该在ProgressChanged事件处理程序中完成。
  2. 默认情况下,BackGroundWorker不允许报告进度,也不允许取消操作。当您在代码中添加BackGroundWorker 时,如果要调用该代码,则必须将WorkerReportsProgress的{​​{1}}属性设置为BackGroundWorker true。{/ li>的ReportProgress方法
  3. 如果您还需要允许用户取消操作,请将BackGroundWorker设置为true,并在WorkerSupportsCancellation事件处理程序的循环中检查您的DoWork中的属性CancellationPending
  4. 我希望我帮助

答案 5 :(得分:4)

您所显示的代码存在一些基本问题。正如其他人所提到的,Application.DoEvents()将无法从后台线程中执行您想要的操作。将慢速后台处理分成BackgroundWorker并更新UI线程中的进度条。您正在从后台线程调用progressBar1.Value++,这是错误的。

永远不要拨打Control.CheckForIllegalCrossThreadCalls = false,这只会隐藏错误。

如果要从后台线程更新进度条,则必须实现ProgressChanged处理程序;你没有这样做。

如果您需要实施BackgroundWorker的示例,请参阅代码项目中的文章BackgroundWorker Threads

答案 6 :(得分:3)

我认为编写线程安全应用程序是一个比较难理解和正确操作的事情。你真的需要阅读它。如果您从一本书中学习了C#,请回过头来看看是否没有关于多线程的章节。我从Andrew Troelsen的书中了解到(最新的是 Pro C#2005和.NET 2.0平台),直到第14章他都没有触及这个主题(所以当时有很多人已经放弃了阅读) 。我来自嵌入式编程背景,其中并发性和原子性也是关注点,所以这不是.NET或Windows特定的问题。

这里的很多帖子都详细介绍了通过.NET提供的工具处理线程的机制。所有宝贵的建议和你必须学习的东西,但如果你先熟悉“理论”,它将会有所帮助。采取跨线程问题。真正发生的是UI线程有一些内置的复杂性,可以检查你是否从另一个线程修改一个控件。 MS的设计人员意识到这是一个容易犯的错误,因此可以对其进行内置保护。为什么危险?这需要您了解原子操作是什么。由于控件的“状态”不能在原子操作中更改,因此一个线程可以开始更改控件,然后另一个线程可以变为活动状态,从而使控件处于部分修改状态。现在,如果你告诉用户界面我没有给出一个废话只是让我的线程修改控件,它可能会工作。但是,当它不起作用时,你将很难找到bug。你将使代码的可维护性降低,跟随你的程序员会诅咒你的名字。

因此UI非常复杂并且会检查您,您正在编写的类和线程不会。您必须了解类和线程中的原子。您可能会在线程中导致“冻结”。最常见的是,应用程序冻结是死锁情况的结果。在这种情况下,“冻结”意味着UI永远不会再次响应。无论如何,这已经太长了,但我认为至少你应该熟悉“锁定”的使用,可能还有Monitor,interlocked,semaphore和mutex。

只是为了给你一个想法:(来自Troelsen的书)

intVal++; //This is not thread safe 
int newVal = Interlocked.Increment(ref intVal); //This is thread safe

.NET还提供[同步]属性。这可以使编写线程安全类变得容易,但是你确实为使用它而付出了高昂的代价。好吧,我只是希望能让你了解所涉及的复杂性,并激励你去做进一步的阅读。

答案 7 :(得分:2)

这样做,创建一个新线程

         Thread loginThread = new Thread(new ThreadStart(DoWork));

         loginThread.Start();

在ThreadStart()内部,传递要执行的方法。如果你想在这个方法中更改一些控件属性,那么创建一个委托并将其指向一个方法,在该方法中你将编写控件更改的属性,

         public delegate void DoWorkDelegate(ChangeControlsProperties);         

并调用控件属性这样做,声明一个方法并在其中定义控件new porperties

         public void UpdateForm()
         {
             // change controls properties over here
         }

然后以这种方式将代表指向该方法

         InvokeUIControlDelegate invokeDelegate = new InvokeUIControlDelegate(UpdateForm);

然后当你想在任何地方更改属性时,只需调用它,

         this.Invoke(invokeDelegate);

希望这段代码片段对您有所帮助! :)

答案 8 :(得分:1)

要添加到freedompeace的解决方案,他是对的。此解决方案为thread-safe,这正是您想要的。您只需使用BeginInvoke代替Invoke

void SetControlText(Control control, string text)
{
    control.BeginInvoke(
        new MethodInvoker(() =>
        {
            control.Text = text;
        })
    );
}

以上修复是最简单,最干净的。

希望这会有所帮助。 :)