关于多线程:Silverlight DataBinding跨线程问题

关于多线程:Silverlight DataBinding跨线程问题

Silverlight DataBinding cross thread issue

我有一个Image控件,其源绑定到对象的属性(图像的字符串url)。 进行服务呼叫后,我使用新的URL更新数据对象。 调用PropertyChanged事件后,该异常在离开我的代码后引发。

数据结构和服务逻辑都是在不了解UI的核心dll中完成的。 无法访问分派器时如何与UI线程同步?

PS:为了获得Dispatcher而访问Application.Current.RootVisual并不是一个解决方案,因为root视觉位于另一个线程上(这是我需要避免的确切异常)。

PPS:这仅是图像控件的问题,绑定到任何其他ui元素,交叉线程问题已为您处理。


1
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => {...});

也看这里。


您是否尝试实现INotifyPropertyChanged?


INotifyPropertyChanged接口用于通知客户端(通常是绑定客户端)属性值已更改。

例如,考虑具有名为FirstName的属性的Person对象。为了提供通用的属性更改通知,Person类型实现INotifyPropertyChanged接口,并在更改FirstName时引发PropertyChanged事件。

为了使更改通知在绑定的客户端和数据源之间的绑定中发生,您的绑定类型应为:

实现INotifyPropertyChanged接口(首选)。

为绑定类型的每个属性提供一个更改事件。

两者都不做。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Windows.Forms;

// Change the namespace to the project name.
namespace TestNotifyPropertyChangedCS
{
    // This form demonstrates using a BindingSource to bind
    // a list to a DataGridView control. The list does not
    // raise change notifications. However the DemoCustomer type  
    // in the list does.
    public partial class Form1 : Form
    {
        // This button causes the value of a list element to be changed.
        private Button changeItemBtn = new Button();

        // This DataGridView control displays the contents of the list.
        private DataGridView customersDataGridView = new DataGridView();

        // This BindingSource binds the list to the DataGridView control.
        private BindingSource customersBindingSource = new BindingSource();

        public Form1()
        {
            InitializeComponent();

            // Set up the"Change Item" button.
            this.changeItemBtn.Text ="Change Item";
            this.changeItemBtn.Dock = DockStyle.Bottom;
            this.changeItemBtn.Click +=
                new EventHandler(changeItemBtn_Click);
            this.Controls.Add(this.changeItemBtn);

            // Set up the DataGridView.
            customersDataGridView.Dock = DockStyle.Top;
            this.Controls.Add(customersDataGridView);

            this.Size = new Size(400, 200);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // Create and populate the list of DemoCustomer objects
            // which will supply data to the DataGridView.
            BindingList<DemoCustomer> customerList = new BindingList<DemoCustomer>();
            customerList.Add(DemoCustomer.CreateNewCustomer());
            customerList.Add(DemoCustomer.CreateNewCustomer());
            customerList.Add(DemoCustomer.CreateNewCustomer());

            // Bind the list to the BindingSource.
            this.customersBindingSource.DataSource = customerList;

            // Attach the BindingSource to the DataGridView.
            this.customersDataGridView.DataSource =
                this.customersBindingSource;

        }

        // Change the value of the CompanyName property for the first  
        // item in the list when the"Change Item" button is clicked.
        void changeItemBtn_Click(object sender, EventArgs e)
        {
            // Get a reference to the list from the BindingSource.
            BindingList<DemoCustomer> customerList =
                this.customersBindingSource.DataSource as BindingList<DemoCustomer>;

            // Change the value of the CompanyName property for the  
            // first item in the list.
            customerList[0].CustomerName ="Tailspin Toys";
            customerList[0].PhoneNumber ="(708)555-0150";
        }

    }

    // This is a simple customer class that  
    // implements the IPropertyChange interface.
    public class DemoCustomer : INotifyPropertyChanged
    {
        // These fields hold the values for the public properties.
        private Guid idValue = Guid.NewGuid();
        private string customerNameValue = String.Empty;
        private string phoneNumberValue = String.Empty;

        public event PropertyChangedEventHandler PropertyChanged;

        // This method is called by the Set accessor of each property.
        // The CallerMemberName attribute that is applied to the optional propertyName
        // parameter causes the property name of the caller to be substituted as an argument.
        private void NotifyPropertyChanged([CallerMemberName] String propertyName ="")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        // The constructor is private to enforce the factory pattern.
        private DemoCustomer()
        {
            customerNameValue ="Customer";
            phoneNumberValue ="(312)555-0100";
        }

        // This is the public factory method.
        public static DemoCustomer CreateNewCustomer()
        {
            return new DemoCustomer();
        }

        // This property represents an ID, suitable
        // for use as a primary key in a database.
        public Guid ID
        {
            get
            {
                return this.idValue;
            }
        }

        public string CustomerName
        {
            get
            {
                return this.customerNameValue;
            }

            set
            {
                if (value != this.customerNameValue)
                {
                    this.customerNameValue = value;
                    NotifyPropertyChanged();
                }
            }
        }

        public string PhoneNumber
        {
            get
            {
                return this.phoneNumberValue;
            }

            set
            {
                if (value != this.phoneNumberValue)
                {
                    this.phoneNumberValue = value;
                    NotifyPropertyChanged();
                }
            }
        }
    }
}

每当我们要更新与UI相关的项目时,该操作应在UI线程中发生,否则您将收到无效的跨线程访问异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Deployment.Current.Dispatcher.BeginInvoke( () =>

{

UpdateUI(); // DO the actions in the function Update UI

});

public void UpdateUI()

{

//to do :Update UI elements here

}

我遇到了与此类似的问题,但这是在Windows窗体中:

我有一个具有自己的线程的类,用于更新有关另一个进程的统计信息,我的UI中有一个控件绑定到该对象。我遇到了跨线程调用问题,这是我如何解决的问题:

1
2
3
4
5
6
7
8
9
10
Form m_MainWindow; //Reference to the main window of my application
protected virtual void OnPropertyChanged(string propertyName)
{
  if(PropertyChanged != null)
    if(m_MainWindow.InvokeRequired)
      m_MainWindow.Invoke(
        PropertyChanged, this, new PropertyChangedEventArgs(propertyName);
    else
      PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}

这似乎很好用,如果有人提出建议,请告诉我。


Application类上RootVisual的属性getter具有导致该异常的线程检查。通过在我的App.xaml.cs中的我自己的属性中存储根可视对象的调度程序来解决此问题:

1
2
3
4
5
6
7
public static Dispatcher RootVisualDispatcher { get; set; }

private void Application_Startup(object sender, StartupEventArgs e)
{
    this.RootVisual = new Page();
    RootVisualDispatcher = RootVisual.Dispatcher;
}

如果然后在App.RootVisualDispatcher而不是Application.Current.RootVisual.Dispatcher上调用BeginInvoke,则不应获取此异常。


推荐阅读