【问题标题】:Updating viewmodel from worker thread throws cross thread issue从工作线程更新视图模型会引发跨线程问题
【发布时间】:2016-04-18 07:40:58
【问题描述】:

最近,我一直在测试实现INotifyPropertyChanged 的类型绑定,并从工作线程引发跨线程问题更新属性。

这里是示例代码:

public class MyViewModel : INotifyPropertyChanged
    {
        private string _name;

        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    OnPropertyChanged("Name");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {

            PropertyChangedEventHandler hanlder = PropertyChanged;
           if(hanlder != null)
                hanlder(this, new PropertyChangedEventArgs(propertyName));

        }
    }

上面的viewmodel已经与windows窗体中的标签文本绑定,并从工作线程更新标签值。

从工作线程更新 label1 文本会导致跨线程问题:

public partial class MainForm : Form
{
    private MyViewModel _myViewModel = new MyViewModel();
    public MainForm()
    {
        InitializeComponent();
        Btn1.Click += Btn1_Click;
        label1.DataBindings.Add("Text", _myViewModel, "Name");

    }

    private void Btn1_Click(object sender, EventArgs e)
    {
        Task.Factory.StartNew(() =>
        {
            _myViewModel.Name = "Updating from worker thread!"; It causes cross thread issue
        });
    }
}

到目前为止,我可以相信这是由于从工作线程更新 UI。是否有任何解决方法可以使其线程安全而不改变按钮单击方法,即可能在视图模型中使线程安全。

【问题讨论】:

  • 使用 Dispatcher 更新 UI

标签: c# winforms c#-4.0 task worker-thread


【解决方案1】:

获取 UI 的 SynchronizationContext(例如,在应用启动时从 UI 线程使用 SynchronizationContext.Current),并将其存储在某个静态变量中(我称之为 uiSynchronizationContext)。

然后在您的 OnPropertyChanged 上执行以下操作:

protected virtual void OnPropertyChanged(string propertyName)
{
   uiSynchronizationContext.Post(
        o => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName))
       ,null
     );
}

或者,如果您希望该操作同步(与启动发送/发布操作的线程同步,在 UI 线程上始终是“同步的”),您可以使用 Send 而不是 Post >

我特别不喜欢在多线程时直接进行数据绑定(我更喜欢在 UI 线程上使用带有更改的图形对象的计时器进行轮询),但这应该可以解决问题。

我承认没有测试过

【讨论】:

  • 我不确定,我需要在 Post 方法的第一个参数中传递什么。
  • @akash88 SynchronizationContext 将执行的回调(在您的情况下,调用 PropertyChanged 事件...我已经使用 C# 6 中的 ?. 对其进行了简化,但是您可以使用handler 方法,就像您在原始问题中所做的那样)
  • 抱歉,SendOrPostCallback 委托需要我错过的参数,我已经更新了答案。这是您传递的state 对象(它是Post 函数的第二个参数)。由于我们没有使用它,我们可以在委托主体上忽略它,但我们需要在 lambda 上声明它。
  • 哇!有用。您能否解释一下上述解决方案中的同步上下文角色。
  • @akash88 如果您想知道SynchronizationContext 的全部内容,可以查看this article。它比我解释得更好:-)
【解决方案2】:

您是否尝试过 CheckForIllegalCrossThreadCalls ?
像这样:

private void Btn1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew(() =>
    {
        CheckForIllegalCrossThreadCalls = false; 
        _myViewModel.Name = "Updating from worker thread!"; It causes cross thread issue
        CheckForIllegalCrossThreadCalls = true;

    });
}

我并不是说这是最好的方法,但是当我使用线程时它对我有用

编辑:

private void Btn1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew(() =>
    {
        _myViewModel.Invoke((MethodInvoker)(() => _myViewModel.Name = "Updating from worker thread!"; ));
    });
}

【讨论】:

  • 我可以让它在点击事件中是线程安全的,但我想让 viewmodel 中的线程安全
  • @HenkHolterman : 你说得对...我用另一种更安全的方法编辑我的帖子
  • @C.Chagnon 是什么让您认为视图模型具有Invoke 方法?为什么会这样?
猜你喜欢
  • 1970-01-01
  • 2018-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多