有没有办法让这段代码线程安全?

时间:2016-06-28 00:56:41

标签: c# multithreading

我正在创建一个带有进度条的Windows窗体程序,该进度条将处理包含许多行的文件。当我执行以下代码时,button2_Click方法中的progressBar1.Maximum调用执行得很好,但PostIncident方法中的那个调用会导致System.InvalidOperationException,其中指出:"跨线程操作无效:控制&#39 ; progressBar1'从其创建的线程以外的线程访问。"

到目前为止,我尝试过一些途径: 1)使PostIncident返回一个bool或magic值,以便我将progressbar1.Maximum调用拉入button2_Click方法。 我的问题是我的线程技能不足以解决从线程中捕获返回值的问题。

2)尝试在进度栏中放置一个锁或信号量。在PostIncident方法中调用最大值,导致同样的错误。

目前,我的解决方案是简单地从项目中删除线程,但我确信有一个优雅的解决方案,我根本没有经验可见。

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

3 个答案:

答案 0 :(得分:2)

查看Safe, Simple Multithreading in Windows FormsHow to: Make Thread-Safe Calls to Windows Forms Controls

您想在UI线程上调用UI更新。

故障:

  • 将业务逻辑分成一个类(业务层)。 EX:Bussiness1.cs
  • 从类中公开一个公共委托以获取与UI相关的代码。
  • 在Form.cs(UI图层)中为您的公司类设置委托值(在本例中为进度条更新)
  • 根据需要,类(Business1.cs)只需要调用委托。 (在UI层实现)
  • UI线程安全:D

Dunno如果这对你来说太先进了,但这确实可以帮助你编写最佳实践。

答案 1 :(得分:1)

由于PostIncident()在一个单独的线程中运行,因此它无权更新表单中的值。这只能从UI线程完成。

有很多方法可以解决这个问题,但标准方法可以在本文中找到:

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

谷歌或搜索StackOverflow for" InvokeRequired"了解更多信息。

答案 2 :(得分:1)

这很简单。

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

如你所知

Invoke(new BarDelegate(UpdateBar));

请记住,只有UI线程才能访问UI控件。尝试在非UI线程中分配或读取UI控件的任何属性时,会出现异常。

在您的情况下,ThreadPool.QueueUserWorkItem在单独的后台主题中运行PostIncident。为了操作UI控件,只需使用Invoke包装语句。

Invoke将语句带入UI线程并执行语句,然后继续返回非UI线程。它充当UI和非UI线程之间的桥梁。

此外,如果您不希望UI线程减慢后台线程,则可以使用BeginInvoke。在继续非UI线程之前,BeginInvoke不等待UI线程执行。小心种族问题。