【问题标题】:Threadsafe 'get' from form slider (TrackBar)从表单滑块(TrackBar)中获取线程安全
【发布时间】:2015-07-31 00:29:48
【问题描述】:

我有一个初始表单的应用程序,该表单具有各种配置(复选框、文本框、滑块/轨迹栏)。当我启动使用这些配置的操作时,会调用一个工作进程:

private void btnx_Click(object sender, EventArgs e)
{
  bgWorkerX.RunWorkerAsync();
}

private void bgWorkerX_DoWork(object sender, DoWorkEventArgs e)
{
    Dictionary<string, object> dictCfgOut = GenerateConfigDict();
    XGen.CreateX.aReadFile(inputPathXRAW.Text,  outputPathX.Text, dictCfgOut);
}

GenerateConfigDict 函数创建一个Dictionary&lt;string, object&gt; 的配置数据。这是因为所需的数据不同(一些布尔值、一些整数、一个字符串数组)。创建它的代码如下:

    private Dictionary<string, object> GenerateConfigDict()
    {
        Dictionary<string, object> ConfigDict = new Dictionary<string, object>();

        if (cbXAddOnus.Checked)
        {
            if (!MiscTools.ValidateX(txtInstX.Text, true))
                return null;
        }

        ConfigDict.Add("Available Images", GenerateConfigImageArray());
        ConfigDict.Add("X SCSC Hot", (cbXAddSCSC.Checked) ? (bool)true : (bool)false);
        ConfigDict.Add("X Req Adj", (cbXAddAdjustment.Checked) ? (bool)true : (bool)false);
        ConfigDict.Add("X Items", (cbXAddX.Checked) ? (bool)true : (bool)false);
        ConfigDict.Add("X Use Something", (cbXAddX.Checked) ? (bool)true : (bool)false);
        ConfigDict.Add("X Somedata", (string)txtX.Text);
        ConfigDict.Add("X Use SAN", (cbX.Checked) ? (bool)true : (bool)false);
        ConfigDict.Add("X % Y", (int)sliderX.Value);

        return ConfigDict;
    }

sliderX.Value 部分是今天刚刚添加的。在此之前,一切都很好。然而,只有这一点,我得到:

跨线程操作无效:控件“sliderX”从 线程不是创建它的线程。

首先,为什么读取该值不是线程安全的,但读取复选框和文本是?其次,我一直在讨论如何使该线程安全,但这似乎都需要另一个函数来异步调用该值。我已经采取措施确保在工作人员运行时禁用滑块,因此无法从表单进行更改。

谢谢!

【问题讨论】:

    标签: c# winforms thread-safety trackbar


    【解决方案1】:

    要以线程安全的方式访问任何控件,您应该调用该控件的 Invoke() 方法 (MSDN tutorial)。

    因此,您的代码将如下所示:

    delegate int GetSliderValueCallback();
    
    private int GetValue()
    {
        int sliderValue;
        if (sliderX.InvokeRequired)
        {
            GetSliderValueCallback cb = new GetSliderValueCallback(GetValue);
            return (int)sliderX.Invoke(cb);
        }
        else
        {
            return (int)sliderX.Value;
        }
    }
    

    并在读取值时调用它:

    ConfigDict.Add("X % Y", GetValue());
    

    我也建议你使用async methods。

    private async void btnx_Click(object sender, EventArgs e)
    {
      await bgWorkerX_DoWorkAsync();
    }
    
    private async void bgWorkerX_DoWorkAsync()
    {
        Dictionary<string, object> dictCfgOut = await GenerateConfigDictAsync();
        XGen.CreateX.aReadFile(inputPathXRAW.Text,  outputPathX.Text, dictCfgOut);
    }
    
    private Task<Dictionary<string, object>> GenerateConfigDictAsync()
        {
            return Task.Run(() => {
            Dictionary<string, object> ConfigDict = new Dictionary<string, object>();
    
            if (cbXAddOnus.Checked)
            {
                if (!MiscTools.ValidateX(txtInstX.Text, true))
                    return null;
            }
    
            ConfigDict.Add("Available Images", GenerateConfigImageArray());
            ConfigDict.Add("X SCSC Hot", (cbXAddSCSC.Checked) ? (bool)true : (bool)false);
            ConfigDict.Add("X Req Adj", (cbXAddAdjustment.Checked) ? (bool)true : (bool)false);
            ConfigDict.Add("X Items", (cbXAddX.Checked) ? (bool)true : (bool)false);
            ConfigDict.Add("X Use Something", (cbXAddX.Checked) ? (bool)true : (bool)false);
            ConfigDict.Add("X Somedata", (string)txtX.Text);
            ConfigDict.Add("X Use SAN", (cbX.Checked) ? (bool)true : (bool)false);
            ConfigDict.Add("X % Y", (int)sliderX.Value);
    
            return ConfigDict;
        });
    }
    

    警告!很遗憾,我没有机会构建该代码(我只是在这里编写的),所以请作为示例使用它。。 p>

    【讨论】:

    • 是否可以使用对象 GetValue(object obj) 调用 GetValue(),以便一次调用可以从不同控件获取值?开始构建 dict 的全部目的是推送所有配置数据,但是我必须开始推送的越往后,我就越只是将数据从一个地方推到另一个地方。
    • 当然,Invoke() 的返回类型是 Object(这就是为什么我将它显式转换为 int 的原因)。但是参数类型必须是 Control 或者你应该安全地转换为它: var control = obj as Control; if (control == null) return null;
    • 异步运行工作线程有什么好处?当工作人员很忙时,除了等待(他们被告知)之外,用户不应该对应用程序做任何事情,工作人员自己会经历一个迭代过程,多达 1000 次,必须严格执行订购。
    • @JesseWilliams,请阅读我之前在答案中提供的 MSDN 上的文章。简而言之:1)它会在需要时自动处理 UI 线程的编组; 2)并行计算的速度(当然,如果你需要的话); 3)异常处理。在您的情况下,如果您的操作需要时间(例如,读取文件)并禁用按钮“btnx”,您可以向用户显示一些进度条。无论如何,这取决于你;)
    【解决方案2】:

    控件的线程行为与 .NET 封装的非托管代码有关。我不确切知道从任何线程访问哪个属性可以和/或应该以及为什么。

    除非您需要“实时”访问控件(即更新ProgressBar 值),否则我不会使用Invoke 回调UI 线程。相反,在运行后台工作者之前,在 UI 线程上收集 UI 内容并将它们提供给工作者:

    bgWorkerX.RunWorkerAsync((int)sliderX.Value);
    ...
    private void bgWorkerX_DoWork(object sender, DoWorkEventArgs e)
    {
        Dictionary<string, object> dictCfgOut = GenerateConfigDict((int)e.Argument);
        ...
    }
    
    private Dictionary<string, object> GenerateConfigDict(int sliderValue)
    {
        ...
        ConfigDict.Add("X % Y", sliderValue);
        ...
    }
    

    您甚至可以选择在 UI 线程上创建整个配置字典,然后将其用作后台工作人员的 agrument,而不仅仅是这个值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多