所以我有这个对象:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
假设我在不是GUI线程的线程上更改此对象。当我没有引用此类中的任何GUI组件时,如何让此对象在与GUI相同的线程上引发PropertyChanged事件?
答案 0 :(得分:5)
通常情况下,事件订阅者应负责在必要时编组对UI线程的调用。
但是如果有问题的类是特定于UI的(也就是视图模型),那么只要它在UI线程上创建,就可以捕获SynchronizationContext
并使用它来提高像这样的事件:
public class SomeObject : INotifyPropertyChanged
{
private SynchronizationContext syncContext;
public SomeObject()
{
syncContext = SynchronizationContext.Current;
}
private decimal alertLevel;
public decimal AlertLevel
{
get { return alertLevel; }
set
{
if (alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
if (syncContext != null)
syncContext.Post(_ => handler(this, new PropertyChangedEventArgs(propertyName)), null);
else
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
或者,您可以通过构造函数传递SynchronizationContext
。
另一种方法是保持对象完整,但数据通过中间同步绑定源绑定到它,如Update elements in BindingSource via separate task所述。
答案 1 :(得分:2)
for WPF - 添加以下引用:
PresentationFramework.dll
WindowsBase.dll
在后台线程中 - 将需要访问UI的代码包装到dispatcher.Invoke()
中using System.Windows;
using System.Windows.Threading;
...
//this is needed because Application.Current will be NULL for a WinForms application, since this is a WPF construct so you need this ugly hack
if (System.Windows.Application.Current == null)
new System.Windows.Application();
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
//Do Your magic here
}), DispatcherPriority.Render);
使用WinForms
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(() => {
//Do Your magic here
}));
答案 2 :(得分:1)
更好的想法,不使用任何WPF参考:
public class GUIThreadDispatcher {
private static volatile GUIThreadDispatcher itsSingleton;
private WeakReference itsDispatcher;
private GUIThreadDispatcher() { }
public static GUIThreadDispatcher Instance
{
get
{
if (itsSingleton == null)
itsSingleton = new GUIThreadDispatcher();
return itsSingleton;
}
}
public void Init(Control ctrl) {
itsDispatcher = new WeakReference(ctrl);
}
public void Invoke(Action method) {
ExecuteAction((Control ctrl) => DoInGuiThread(ctrl, method, forceBeginInvoke: false));
}
public void BeginInvoke(Action method) {
ExecuteAction((Control ctrl) => DoInGuiThread(ctrl, method, forceBeginInvoke: true));
}
private void ExecuteAction(Action<Control> action) {
if (itsDispatcher.IsAlive) {
var ctrl = itsDispatcher.Target as Control;
if (ctrl != null) {
action(ctrl);
}
}
}
public static void DoInGuiThread(Control ctrl, Action action, bool forceBeginInvoke = false) {
if (ctrl.InvokeRequired) {
if (forceBeginInvoke)
ctrl.BeginInvoke(action);
else
ctrl.Invoke(action);
}
else {
action();
}
}
}
}
并像这样初始化:
private void MainForm_Load(object sender, EventArgs e) {
//setup the ability to use the GUI Thread when needed via a static reference
GUIThreadDispatcher.Instance.Init(this);
...
}
并像这样使用:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
GUIThreadDispatcher.Instance.BeginInvoke(() => {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}}
答案 3 :(得分:0)
这结果是一个干净的实现(相对)。只需要包含一个对WindowsBase.dll
的引用,结果证明它是一个WPF库,所以......,对它不是很满意,但它是一个解决方案......:
public class GUIThreadDispatcher {
private static volatile GUIThreadDispatcher itsSingleton;
private Dispatcher itsDispatcher;
private GUIThreadDispatcher() { }
public static GUIThreadDispatcher Instance
{
get
{
if (itsSingleton == null)
itsSingleton = new GUIThreadDispatcher();
return itsSingleton;
}
}
public void Init() {
itsDispatcher = Dispatcher.CurrentDispatcher;
}
public object Invoke(Action method, DispatcherPriority priority = DispatcherPriority.Render, params object[] args) {
return itsDispatcher.Invoke(method, priority, args);
}
public DispatcherOperation BeginInvoke(Action method, DispatcherPriority priority = DispatcherPriority.Render, params object[] args) {
return itsDispatcher.BeginInvoke(method, priority, args);
}
然后像这样初始化它:
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
GUIThreadDispatcher.Instance.Init(); //setup the ability to use the GUI Thread when needed via a static reference
Application.Run(new MainForm());
}
}
然后像这样使用它:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
GUIThreadDispatcher.Instance.BeginInvoke(() => {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}}
答案 4 :(得分:0)
找到一个更好的答案,而不必使用WeakReference到表单控件和NO WPF引用基于https://lostechies.com/gabrielschenker/2009/01/23/synchronizing-calls-to-the-ui-in-a-multi-threaded-application/和Ivan的答案:
public class GUIThreadDispatcher {
private static volatile GUIThreadDispatcher itsSingleton;
private SynchronizationContext itsSyncContext;
private GUIThreadDispatcher() {}
/// <summary>
/// This needs to be called on the GUI Thread somewhere
/// </summary>
public void Init() {
itsSyncContext = AsyncOperationManager.SynchronizationContext;
}
public static GUIThreadDispatcher Instance
{
get
{
if (itsSingleton == null)
itsSingleton = new GUIThreadDispatcher();
return itsSingleton;
}
}
public void Invoke(Action method) {
itsSyncContext.Send((state) => { method(); }, null);
}
public void BeginInvoke(Action method) {
itsSyncContext.Post((state) => { method(); }, null);
}
}
}
并像这样初始化:
private void MainForm_Load(object sender, EventArgs e) {
//setup the ability to use the GUI Thread when needed via a static reference
GUIThreadDispatcher.Instance.Init();
...
}
并像这样使用:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
GUIThreadDispatcher.Instance.BeginInvoke(() => {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}}