同步上下文和调度程序之间的区别

TRS*_*TRS 38 .net c#

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

Application.Current.Dispatcher.Invoke(myAction);
Run Code Online (Sandbox Code Playgroud)

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

SynchronizationContext.Current.Post(myAction,null);
Run Code Online (Sandbox Code Playgroud)

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

Dir*_*irk 27

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

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

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

您可以SynchronizationContext其MSDN文档中看到继承的类: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*/);
    }
}
Run Code Online (Sandbox Code Playgroud)

这不适用于Application.Current.Dispatcher,它始终返回应用程序的调度程序.

  • 小注 - Task.ContinueWith允许您在ui上安排延续而无需手动存储上下文http://stackoverflow.com/questions/4331262/task-continuation-on-ui-thread (2认同)

Moe*_*bai 21

使用时WPF,SynchronizationContext.Current对象的类型DispatcherSynchronizationContext实际上只是Dispatcher对象周围的包装,PostSend方法只是委托给Dispatcher.BeginInvokeDispatcher.Invoke.

所以即使你决定使用SynchronizationContext我认为你最终会在幕后调用调度员.

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

  • @NikhilAgrawal http://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/DispatcherSynchronizationContext.cs (4认同)
  • 正如其他答案中所解释的,使用 `SynchronizationContext` 而不是 `Dispatcher` 可以从逻辑中抽象 UI,有助于在单元测试时模拟 UI,并且更适合 MVVM (2认同)

Sta*_*itz 12

虽然已经指出了差异,但我并没有真正看到在这里明确阐述的选择一个而不是另一个的理由。因此,也许这将有助于解释SynchronizationContext对象首先要解决的问题:

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

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

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

  • 关注点分离

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

  • 单元测试更轻松

如果您曾经尝试模拟与Dispatcher和SynchronizationContext一样复杂的对象,而该对象要处理的方法要少得多,那么您很快就会体会到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();
                });
            });
    }
Run Code Online (Sandbox Code Playgroud)

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

  1. 该代码绑定到WPF 应用程序 调度程序对象。这使得这很难进行单元测试和抽象。
  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);
        });
    }
Run Code Online (Sandbox Code Playgroud)

请注意,这一次,我们不需要依赖外部对象在线程之间进行同步。实际上,我们正在上下文之间进行同步。

现在,即使您没有要求,但出于完整性考虑,还有另一种方法可以抽象地完成您想要的事情,而无需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;
    }
Run Code Online (Sandbox Code Playgroud)

正如他们所说,安排任务有多种方法。)


Vad*_* S. 5

SynchronizationContext是使用虚方法的抽象。使用 SynchronizationContext允许您不将您的实现绑定到特定框架。

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