简体   繁体   中英

Is there a way to make this code thread-safe?

I am creating a windows forms program with a progress bar that will process a file with many rows. When I execute the following code, the progressBar1.Maximum call in the button2_Click method executes just fine but the one in the PostIncident method results in a System.InvalidOperationException which states: "Cross-thread operation not valid: Control 'progressBar1' accessed from a thread other than the thread it was created on."

A few avenues that I have tried so far: 1) Making PostIncident return a bool or magic value so that I pull the progressbar1.Maximum call up into the button2_Click method. My issues there is my skill with threading is not sufficient to get around the problems with capturing a returned value from a thread.

2) Tried putting a lock or semaphore around the progressbar1.Maximum call in the PostIncident method which resulted in the same error.

At the moment my solution is to simply remove threading from the project entirely but I'm sure there is probaly an elegant solution I am simply too inexperienced to see.

public partial class ExperianTriggerPoster : Form
{
    private readonly OpenFileDialog _ofd = new OpenFileDialog();
    public delegate void BarDelegate();
    private string _path;

    private void button1_Click(object sender, EventArgs e)
    {
        if (_ofd.ShowDialog() != DialogResult.OK) return;
        textBox1.Text = _ofd.SafeFileName;
        _path = _ofd.FileName;
    }

    private void button2_Click(object sender, EventArgs e)
    {
        string[] sAllLinesFromFile = File.ReadAllLines(_path);

        foreach (string line in sAllLinesFromFile)
        {
            if (!line.StartsWith("N"))
            {
                progressBar1.Maximum -= 1;
                continue;
            }

            //some logic here...
            ThreadPool.QueueUserWorkItem(x => PostIncident(//some parameters here...));
        }
    }

    private void PostIncident(//some parameters here...)
    {
        //some logic here...
        if (customerNo == "not found") // must find a way to make this call thread-safe
        {
            Log.Information("Could not find customer# for user#: " + userNo);
            progressBar1.Maximum -= 1;
        }

        Invoke(new BarDelegate(UpdateBar));
    }

    private void UpdateBar()
    {
        progressBar1.Value++;

        if (progressBar1.Value != progressBar1.Maximum) return;

        var postingComplete = MessageBox.Show("The posting is complete!", "Experian Trigger Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
        if (postingComplete == DialogResult.OK) Environment.Exit(0);
    }
}

Take a look on Safe, Simple Multithreading in Windows Forms or How to: Make Thread-Safe Calls to Windows Forms Controls .

You would like to invoke UI updates on the UI thread.

Breakdown:

  • Separate business logic into a class (Business layer). EX: Bussiness1.cs
  • Expose a public delegate from the class for the UI related codes.
  • Set the delegate value (In this case progress bar update) for your business class inside the Form.cs (UI layer)
  • As needed, the class (Business1.cs) will only need to invoke the delegate. (Implementation in UI layer)
  • UI Thread safe :D

Dunno if this is too advanced for you but this will really help you code in best practices.

Since PostIncident() is being run in a separate thread, it does not have access to update the values in the form. This can only be done from the UI thread.

There are a number of ways around this, but the standard way can be found in this article:

How to: Make Thread-Safe Calls to Windows Forms Control s

Google or search StackOverflow for "InvokeRequired" for more information.

It is so simple.

Invoke(new BarDelegate(() => progressBar1.Maximum -= 1));

as you know using

Invoke(new BarDelegate(UpdateBar));

Remember that only UI thread gets access to UI controls. Trying to assign or read any property of a UI control in non-UI thread, you get an exception.

In your case, ThreadPool.QueueUserWorkItem runs PostIncident in a seperated background thread. In order to manipulate UI controls, just wrap statement with Invoke .

Invoke brings statements into UI thread and executes statements and then continues back in non-UI thread. It acts as a bridge between UI and non-UI threads.

Moreover, BeginInvoke can be used if you don't want UI thread slowing your background thread. BeginInvoke does not wait UI thread execution before continuing non-UI thread. Just be careful of race issue.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM