开发者

How to use multithreading with Winform?

I'm a newbie with multithreading. I have a winform that have a label and a progress bar.

I wanna show the processing result. Firstly, I use Application.DoEvents() method. But, I find that the form is freezing.

Then I read some article about multithreading at MSDN.

Secondly, I use a BackgroundWorker to do it.

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

The form doesn't freeze that I can drag/drog when processing. Unfortunately, it throws an InvalidOperationException. So I have to use this. Control.CheckForIllegalCrossThreadCalls = false; I ensure that's not a final solution.

Do you have some suggestion to do so, Experts?

Edit: When I call listview throw InvalidOperationException. This code is in DoWork().

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

Edit2: I use delegate and invoke in DoWork() and it don't throw exeption. But the form is freezing again. How to do it so the form could be droppable/drappable while processing?


You can only set progress bar user control properties from the UI thread (the WinForm one). The easiest way to do it with your BackgroundWorker is to use the ProgressChanged event:

private BackgroundWorker bwForm;
private ProgressBar progressBar;

In WinForm constructor:

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

see here: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

EDIT

So I didn't understand your question, I thought it was about showing progress bar progression during work. If it's about result of work use e.Result in the BwForm_DoWork event. Add a new event handler for completed event and manage 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
    }
}


Invoke the UI thread. For example:

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


Avoid calling Application.DoEvents. Often times this leads to more problems than it solves. It is generally considered bad practice because there exists better alternatives which keep the UI pumping messages.

Avoid changing setting CheckForIllegalCrossThreadCalls = false. This does not fix anything. It only masks the problem. The problem is that you are attempting to access a UI element from a thread other than the main UI thread. If you disable CheckForIllegalCrossThreadCalls then you will no longer get the exception, but instead your application will fail unpredictably and spectacularly.

From within the DoWork event handler you will want to periodically call ReportProgress. From your Form you will want to subscribe to the ProgressChanged event. It will be safe to access UI elements from within the ProgressChanged event handler because it is automatically marshaled onto the UI thread.


You should use Control.Invoke(). Also see this question for details.


Cleanest way is using BackGroundWorker as you did.

you just missed some points:

  1. you cannot access your form elements in DoWork event handler because that makes a cross thread method invokation, this should be done in ProgressChanged event handler.
  2. by default a BackGroundWorker will not allow to report progress, as well as in won't allow Cancelling the operation. When you add a BackGroundWorker to your code, you have to set the WorkerReportsProgress property of that BackGroundWorker to true if you want to call the ReportProgress method of your BackGroundWorker.
  3. In case you need to allow the user to cancel the operation as well, set WorkerSupportsCancellation to true and in your loop in the DoWork event handler check the property named CancellationPending in your BackGroundWorker

I hope I helped


There are some fundamental problems with the code you have shown. As other have mentioned, Application.DoEvents() will not do what you want from a background thread. Separate your slow background processing into a BackgroundWorker and update your progress bar in the UI thread. You are calling progressBar1.Value++ from a background thread, which is wrong.

Never call Control.CheckForIllegalCrossThreadCalls = false, that will only hide errors.

If you want to update a progress bar from a background thread, you will have to implement a ProgressChanged handler; you are not doing that.

If you need an example of implementing a BackgroundWorker, consult the article BackgroundWorker Threads from Code Project.


I would say writing thread safe applications is one of the more difficult things to understand and do properly. You really need to read up on it. If you learned C# from a book go back and see if there isn't a chapter on Multithreading. I learned from Andrew Troelsen's books (the latest was Pro C# 2005 and the .NET 2.0 Platform), he doesn't touch this topic until Chapter 14 (so a lot of people have quit reading by then). I come from an embedded programming background where concurrency and atomicity are also concerns so this is not a .NET or Windows specific issue.

A lot of those posts here are detailing the mechanics of dealing with threads via the facilities supplied in .NET. All valuable advice and things you have to learn but it will really help if you become more familiar with the "theory" first. Take the cross-threading issue. What's really going on is the UI thread has some built-in sophistication that checks to see if you are modifying a control from a different thread. The designers at MS realized this was an easy mistake to make so the built in protection against it. Why is it dangerous? That requires you understand what an atomic operation is. Since the control's "state" can't be changed in an atomic operation, one thread could start to change the control and then another thread could become active, thus leaving the control in a partially modified state. Now if you tell the UI I don't give a crap just let my thread modify the control, it might work. However, when it doesn't work, you'll have a very hard to find bug. You'll have made your code much less maintainable and the programmers that follow you will curse your name.

So the UI is sophisticated and checks for you, the classes and threads you are writing don't. You have to understand what is atomic in your classes and threads. You might be causing the "freeze" in your threads. Most commonly an app freezing is the result of a deadlock condition. In this case "freeze" means the UI never becomes responsive again. At any rate this is getting too long but I think at a minimum you should be familiar with the use of "lock" and probably also Monitor, interlocked, semaphore and mutex.

Just to give you an idea: (from Troelsen's book)

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

.NET also provides a [Synchronization] attribute. This can make writing a thread safe class easy, but you do pay a price in efficiency for using it. Well I'm just hoping to give you some idea of the complexities involved and motivate you to go do some further reading.


Do like this, create a new thread

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

         loginThread.Start();

Inside the ThreadStart(), pass the method you want to execute. If inside this method you want to change somes controls properties then create a delegate and point it to a method inside which you will be writing the controls changed properties,

         public delegate void DoWorkDelegate(ChangeControlsProperties);         

and invoke the controls properties do like this, declare a method and inside it define the controls new porperties

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

then point a delegate to the method, in this way,

         InvokeUIControlDelegate invokeDelegate = new InvokeUIControlDelegate(UpdateForm);

then when you want to change the properties at any place just call this,

         this.Invoke(invokeDelegate);

Hope this code snippet helps you ! :)


To add onto freedompeace's solution, he was right. This solution is thread-safe and that's exactly what you want. You just need to use BeginInvoke instead of Invoke.

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

The above fix is the simplest and cleanest.

Hope this helps. :)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜