我正在使用Dispatcher
从外部切换到UI线程
Application.Current.Dispatcher.Invoke(myAction);
但我在一些论坛上看到人们建议使用Synchronization
上下文而不是Dispatcher
。
SynchronizationContext.Current.Post(myAction,null);
它们之间有什么区别,为什么应该使用SynchronizationContext
?
答案 0 :(得分:24)
它们都有类似的效果,但SynchronizationContext
更通用。
Application.Current.Dispatcher
引用应用程序的WPF调度程序,并使用Invoke
on执行该应用程序主线程上的委托。
SynchronizationContext.Current
返回取决于当前线程的不同实现。当在WPF应用程序的UI线程上调用时,它返回一个使用调度程序的SynchronizationContext
,当在WinForms应用程序的UI线程上调用它时,它返回另一个。
您可以在its MSDN documentation中看到继承自SynchronizationContext
的类:WindowsFormsSynchronizationContext和DispatcherSynchronizationContext。
使用SynchronizationContext
时需要注意的一点是,它返回当前线程的同步上下文。如果要使用另一个线程的同步上下文,例如在UI线程中,您必须首先获取其上下文并将其存储在变量中:
public void Control_Event(object sender, EventArgs e)
{
var uiContext = SynchronizationContext.Current;
Task.Run(() =>
{
// do some work
uiContext.Post(/* update UI controls*/);
}
}
这不适用于Application.Current.Dispatcher
,它始终返回应用程序的调度程序。
答案 1 :(得分:18)
使用WPF
时,SynchronizationContext.Current
对象的类型为DispatcherSynchronizationContext
,它实际上只是Dispatcher
对象和Post
和{{的包装器1}}方法只委托给Send
和Dispatcher.BeginInvoke
。
所以即使你决定使用Dispatcher.Invoke
,我认为你最终会在幕后调用调度员。
此外我认为使用SynchronizationContext
有点麻烦,因为您必须将对当前上下文的引用传递给需要调用UI的所有线程。
答案 2 :(得分:5)
虽然已经指出了差异,但我并没有真正看到在这里明确说明选择一个而不是另一个的原因。所以也许这有助于解释 SynchronizationContext 对象首先尝试解决的问题:
因此,为了回答您选择哪一个的问题,从上面的标准来看,使用SynchronizationContext将优于Dispatcher。
但是还有更有说服力的理由:
通过使用SynchronizationContext处理UI线程上的执行代码,您现在可以通过解耦接口轻松地将操作与显示分开。这导致了下一点:
如果您曾尝试模拟像Dispatcher和SynchronizationContext这样复杂的对象,它只需要很少的方法来处理,您很快就会发现SynchronizationContext提供的更简单的接口。
正如您已经看到的,SynchronizationContext是在许多UI框架中实现的:WinForms,WPF,ASP.NET等。如果您编写代码以连接到一组API,您的代码将变得更加便携,更易于维护以及测试。
您甚至不需要注入上下文对象...您可以使用与上下文对象上的方法匹配的接口注入任何对象,包括代理。
举例:
注意:我遗漏了异常处理以使代码清晰。
假设我们有一个只有一个按钮的WPF应用程序。单击该按钮后,您将启动与UI更新交错的长时间异步工作任务,您需要在两者之间协调IPC。
使用WPF和传统的Dispatch方法,您可能会编写如下代码:
/// <summary>
/// Start a long series of asynchronous tasks using the Dispatcher for coordinating
/// UI updates.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Start_Via_Dispatcher_OnClick(object sender, RoutedEventArgs e)
{
// update initial start time and task status
Time_Dispatcher.Text = DateTime.Now.ToString("hh:mm:ss");
Status_Dispatcher.Text = "Started";
// create UI dont event object
var uiUpdateDone = new ManualResetEvent(false);
// Start a new task (this uses the default TaskScheduler,
// so it will run on a ThreadPool thread).
Task.Factory.StartNew(async () =>
{
// We are running on a ThreadPool thread here.
// Do some work.
await Task.Delay(2000);
// Report progress to the UI.
Application.Current.Dispatcher.Invoke(() =>
{
Time_Dispatcher.Text = DateTime.Now.ToString("hh:mm:ss");
// signal that update is complete
uiUpdateDone.Set();
});
// wait for UI thread to complete and reset event object
uiUpdateDone.WaitOne();
uiUpdateDone.Reset();
// Do some work.
await Task.Delay(2000); // Do some work.
// Report progress to the UI.
Application.Current.Dispatcher.Invoke(() =>
{
Time_Dispatcher.Text = DateTime.Now.ToString("hh:mm:ss");
// signal that update is complete
uiUpdateDone.Set();
});
// wait for UI thread to complete and reset event object
uiUpdateDone.WaitOne();
uiUpdateDone.Reset();
// Do some work.
await Task.Delay(2000); // Do some work.
// Report progress to the UI.
Application.Current.Dispatcher.Invoke(() =>
{
Time_Dispatcher.Text = DateTime.Now.ToString("hh:mm:ss");
// signal that update is complete
uiUpdateDone.Set();
});
// wait for UI thread to complete and reset event object
uiUpdateDone.WaitOne();
uiUpdateDone.Reset();
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult()
.ContinueWith(_ =>
{
Application.Current.Dispatcher.Invoke(() =>
{
Status_Dispatcher.Text = "Finished";
// dispose of event object
uiUpdateDone.Dispose();
});
});
}
此代码按预期工作,但有以下缺点:
现在,让我们使用 SynchronizationContext 对象再次尝试:
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Start_Via_SynchronizationContext_OnClick(object sender, RoutedEventArgs e)
{
// update initial time and task status
Time_SynchronizationContext.Text = DateTime.Now.ToString("hh:mm:ss");
Status_SynchronizationContext.Text = "Started";
// capture synchronization context
var sc = SynchronizationContext.Current;
// Start a new task (this uses the default TaskScheduler,
// so it will run on a ThreadPool thread).
Task.Factory.StartNew(async () =>
{
// We are running on a ThreadPool thread here.
// Do some work.
await Task.Delay(2000);
// Report progress to the UI.
sc.Send(state =>
{
Time_SynchronizationContext.Text = DateTime.Now.ToString("hh:mm:ss");
}, null);
// Do some work.
await Task.Delay(2000);
// Report progress to the UI.
sc.Send(state =>
{
Time_SynchronizationContext.Text = DateTime.Now.ToString("hh:mm:ss");
}, null);
// Do some work.
await Task.Delay(2000);
// Report progress to the UI.
sc.Send(state =>
{
Time_SynchronizationContext.Text = DateTime.Now.ToString("hh:mm:ss");
}, null);
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult()
.ContinueWith(_ =>
{
sc.Post(state =>
{
Status_SynchronizationContext.Text = "Finished";
}, null);
});
}
注意这一次,我们不需要依赖外部对象来在线程之间进行同步。事实上,我们正在 contexts 之间进行同步。
现在,即使你没有提出要求,但为了完整起见,还有一种方法可以用抽象的方式完成你想要的东西而不需要 SynchronizationContext 对象或使用调度程序。由于我们已经在使用TPL(任务并行库)进行任务处理,我们可以按如下方式使用任务调度程序:
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Start_Via_TaskScheduler_OnClick(object sender, RoutedEventArgs e)
{
Time_TaskScheduler.Text = DateTime.Now.ToString("hh:mm:ss");
// This TaskScheduler captures SynchronizationContext.Current.
var taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Status_TaskScheduler.Text = "Started";
// Start a new task (this uses the default TaskScheduler,
// so it will run on a ThreadPool thread).
Task.Factory.StartNew(async () =>
{
// We are running on a ThreadPool thread here.
// Do some work.
await Task.Delay(2000);
// Report progress to the UI.
var reportProgressTask = ReportProgressTask(taskScheduler, () =>
{
Time_TaskScheduler.Text = DateTime.Now.ToString("hh:mm:ss");
return 90;
});
// get result from UI thread
var result = reportProgressTask.Result;
Debug.WriteLine(result);
// Do some work.
await Task.Delay(2000); // Do some work.
// Report progress to the UI.
reportProgressTask = ReportProgressTask(taskScheduler, () =>
{
Time_TaskScheduler.Text = DateTime.Now.ToString("hh:mm:ss");
return 10;
});
// get result from UI thread
result = reportProgressTask.Result;
Debug.WriteLine(result);
// Do some work.
await Task.Delay(2000); // Do some work.
// Report progress to the UI.
reportProgressTask = ReportProgressTask(taskScheduler, () =>
{
Time_TaskScheduler.Text = DateTime.Now.ToString("hh:mm:ss");
return 340;
});
// get result from UI thread
result = reportProgressTask.Result;
Debug.WriteLine(result);
},
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.Default)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult()
.ContinueWith(_ =>
{
var reportProgressTask = ReportProgressTask(taskScheduler, () =>
{
Status_TaskScheduler.Text = "Finished";
return 0;
});
reportProgressTask.Wait();
});
}
/// <summary>
///
/// </summary>
/// <param name="taskScheduler"></param>
/// <param name="func"></param>
/// <returns></returns>
private Task<int> ReportProgressTask(TaskScheduler taskScheduler, Func<int> func)
{
var reportProgressTask = Task.Factory.StartNew(func,
CancellationToken.None,
TaskCreationOptions.None,
taskScheduler);
return reportProgressTask;
}
正如他们所说,安排任务的方法不止一种; )
答案 3 :(得分:1)
SynchronizationContext是使用虚拟方法的抽象。使用SynchronizationContext可以使您不必将实现绑定到特定框架。
示例:Windows窗体使用WindowsFormSynchronizationContext重写Post来调用Control.BeginInvoke。 WPF使用覆盖Post的DispatcherSynchronizationContext类型来调用Dispatcher.BeginInvoke。您可以设计使用SynchronizationContext且不将实现绑定到特定框架的组件。
答案 4 :(得分:0)
如果您确定在用户界面线程的上下文中调用Dispatcher
类,那么SynchronizationContext
类非常有用,Dispatcher
在您不太确定时很有用。
如果在某些非UI线程上使用static Dispatcher.CurrentDispatcher
方法获取BeginInvoke
类并调用SynchronizationContext
方法,则不会发生任何事情(无例外,没有警告,nada)。
但是,如果通过静态SynchronizationContext.Current
方法获取moment().format('dddd MMMM Do YYYY');
类,则如果该线程不是UI线程,则返回null。这种羽毛非常有用,因为它允许您相应地对UI线程和非UI线程做出反应。
答案 5 :(得分:-2)
“当您确定在用户界面线程的上下文中调用时,Dispatcher类很有用” - 您正在使用Dispatcher从非UI线程转到UI线程。这是BeginInvoke的特性。如果从UI线程调用它,那么代码只是在UI线程中执行。没有上下文切换。如果您不想调用BeginInvoke,在UI线程中,您可以调用Dispatcher.Current.CheckAccess(),如果您不在GUI线程中则返回false