【问题标题】:Difference between Synchronization Context and Dispatcher同步上下文和调度程序之间的区别
【发布时间】:2019-12-10 23:20:47
【问题描述】:

我正在使用Dispatcher 像这样从外部切换到 UI 线程

Application.Current.Dispatcher.Invoke(myAction);

但我在一些论坛上看到人们建议使用 SynchronizationContext 而不是 Dispatcher

SynchronizationContext.Current.Post(myAction,null);

它们有什么区别,为什么要使用SynchronizationContext

【问题讨论】:

  • 哪个论坛,他们的论点是什么?
  • 可能是一个愚蠢的问题,但“Application.Current.Dispatcher.Invoke(myAction);”是什么命名空间来自??抱歉,我是新手!

标签: c# .net


【解决方案1】:

它们都有类似的效果,但SynchronizationContext 更通用。

Application.Current.Dispatcher 指的是应用程序的 WPF 调度程序,并使用 Invoke 在该应用程序的主线程上执行委托。

SynchronizationContext.Current 另一方面,根据当前线程返回不同的实现。当在 WPF 应用程序的 UI 线程上调用时,它返回一个使用调度程序的 SynchronizationContext,当在 WinForms 应用程序的 UI 线程上调用时,它返回一个不同的。

您可以在its MSDN documentation 中看到从SynchronizationContext 继承的类:WindowsFormsSynchronizationContextDispatcherSynchronizationContext


使用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,它总是返回应用程序的调度程序。

【讨论】:

  • 小记 - Task.ContinueWith 让您可以在 ui 上安排延续,而无需手动存储上下文 stackoverflow.com/questions/4331262/…
  • @Gusdor 当然,但重点是展示SynchronizationContext 的示例。
  • @Gusdor,在您链接的示例中,必须首先创建一个新任务。没有任务继续使用Task.ContinueWith()怎么办?
  • @KyleDelaney 现有任务是ContinueWith 的要求。您是否正在尝试解决特定问题?也许最好提出一个新问题。
  • 好吧,我要指出为什么您的建议不能替代 SynchronizationContext 的这种用法。
【解决方案2】:

使用WPF 时,SynchronizationContext.Current 对象的类型为DispatcherSynchronizationContext,它实际上只是Dispatcher 对象的包装,PostSend 方法只是委托给Dispatcher.BeginInvoke 和@ 987654329@.

因此,即使您决定使用 SynchronizationContext,我认为您最终还是会在幕后调用调度程序。

此外,我认为使用 SynchronizationContext 有点麻烦,因为您必须将对当前上下文的引用传递给需要调用 UI 的所有线程。

【讨论】:

【解决方案3】:

虽然已经指出了差异,但我并没有真正看到这里明确说明选择一个而不是另一个的原因。所以也许这有助于解释 SynchronizationContext 对象首先试图解决的问题:

  1. 它提供了一种将工作单元排队到上下文的方法。请注意,这不是特定于线程的,因此我们避免了线程关联问题。
  2. 每个线程都有一个“当前”上下文,但该上下文可能在线程之间共享,即上下文不一定是唯一的。
  3. 上下文保留未完成的异步操作的计数。这个计数通常但并不总是在捕获/排队时增加/减少。

因此,要回答您选择哪一个的问题,仅从上述标准来看,使用SynchronizationContext 似乎比Dispatcher 更可取。

但还有更令人信服的理由这样做:

  • 关注点分离

通过使用SynchronizationContext 处理UI 线程上的执行代码,您现在可以通过解耦接口轻松地将操作与显示分开。这就引出了下一点:

  • 更简单的单元测试

如果您曾经尝试过模拟像 DispatcherSynchronizationContext 那样复杂的对象,后者处理的方法要少得多,您将很快体会到 SynchronizationContext 提供的简单得多的接口.

  • IoC 和依赖注入

如您所见,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();
      });
    });
}

此代码按预期工作,但有以下缺点:

  1. 代码绑定到 WPF Application Dispatcher 对象。这使得它难以进行单元测试和抽象。
  2. 需要一个外部 ManualResetEvent 对象来在线程之间进行同步。这应该会立即引起代码异味,因为这现在取决于需要模拟的另一个资源。
  3. 难以管理所述相同内核对象的对象生命周期。

现在,让我们使用 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;
}

正如他们所说,安排任务的方法不止一种; )

【讨论】:

    【解决方案4】:

    SynchronizationContext 是使用虚方法的抽象。使用SynchronizationContext 可以让您不必将您的实现绑定到特定框架。

    示例:Windows 窗体使用覆盖 Post 的 WindowsFormSynchronizationContext 来调用 Control.BeginInvoke。 WPF 使用覆盖 Post 的 DispatcherSynchronizationContext 类型来调用 Dispatcher.BeginInvoke。您可以设计使用SynchronizationContext 并且不将实现绑定到特定框架的组件。

    【讨论】:

      【解决方案5】:

      Dispatcher 类在您确定是在用户界面线程的上下文中调用时很有用,而SynchronizationContext 在您不太确定时很有用。

      如果您在某个非 UI 线程上使用static Dispatcher.CurrentDispatcher 方法获取您的Dispatcher 类并调用BeginInvoke 方法,则不会发生任何事情(无异常,无警告,nada)。

      但是,如果您通过静态SynchronizationContext.Current 方法获取您的SynchronizationContext 类,如果线程不是UI 线程,它将返回null。此功能非常有用,因为它允许您相应地对 UI 线程和非 UI 线程做出反应。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-04-13
        • 1970-01-01
        • 2015-02-09
        • 1970-01-01
        • 2014-04-27
        • 2021-10-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多