虽然已经指出了差异,但我并没有真正看到这里明确说明选择一个而不是另一个的原因。所以也许这有助于解释 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();
});
});
}
此代码按预期工作,但有以下缺点:
- 代码绑定到 WPF
Application Dispatcher 对象。这使得它难以进行单元测试和抽象。
- 需要一个外部
ManualResetEvent 对象来在线程之间进行同步。这应该会立即引起代码异味,因为这现在取决于需要模拟的另一个资源。
- 难以管理所述相同内核对象的对象生命周期。
现在,让我们使用 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);
});
}
注意这一次,我们不需要依赖外部对象在线程之间进行同步。事实上,我们正在上下文之间进行同步。
现在,即使您没有问,但为了完整起见,还有另一种方法可以以抽象的方式完成您想要的,而不需要 SynchronizationContext 对象或使用 Dispatcher。由于我们已经在使用 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;
}
正如他们所说,安排任务的方法不止一种; )