繁体   English   中英

在异步方法中更改SynchronizationContext

[英]Changing SynchronizationContext Within Async Method

我试图在这里发布一个没有代码的精益问题,因为我的问题是如此具体:是否有可能/可以在Async方法中修改SynchronizationContext 如果我在Async方法开始时未设置SynchronizationContext ,则其中的代码(包括我引发的事件和我调用的同一类模块中的方法)似乎在同一工作线程上运行。 但是,当需要与UI交互时,我发现必须将SynchronizationContext设置为UI线程。

是否可以将SynchronizationContext设置为工作线程,直到我想调用对基于UI的函数的调用?

编辑:为了进一步澄清我上面的问题,我发现自己在SynchronizationContext设置方面处于不赢的局面。 如果不设置SynchronizationContext,那么我的异步操作将在单独的线程上运行(根据需要),但是如果不遇到跨线程操作异常,就无法将数据返回到UI线程。 如果将SynchronizationContext设置为UI线程,那么我想在单独的线程上运行的操作最终将在UI线程上运行-然后(当然)我避免了跨线程异常,并且一切正常。 显然,我缺少了一些东西。

如果您想了解更多信息,我已尝试为我要做的事情提供一个非常清晰的解释,并且我知道您正在投入时间来理解,所以谢谢您!

此流程图显示了我要执行的操作:

在此处输入图片说明

我有一个运行在UI线程上的Winforms应用程序(黑色); 我有一个Socket对象,我想在其自己的线程上运行。 套接字类的工作是从通信套接字读取数据,并在数据到达时将事件引发回UI。

注意,我从UI线程开始一个消息循环,以便不断地在套接字上轮询套接字上的数据。 如果接收到数据,我想在从套接字中获取更多数据之前,在同一个非UI线程上同步处理该数据。 (是的,如果在读取任何特定套接字时出现问题,则该套接字中可能会留下未读的数据。)

启动消息循环的代码如下所示:

if (Socket.IsConnected)
{
   SetUpEventListeners();
   // IMPORTANT: next line seems to be required in order to avoid a cross-thread error
   SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
   Socket.StartMessageLoopAsync();
}

当我从UI线程启动消息循环时,我在Socket对象上调用了一个异步方法:

 public async void StartMessageLoopAsync()
  {
     while (true)
     {
        // read socket data asynchronously and populate the global DataBuffer
        await ReadDataAsync();

        if (DataBuffer.Count == 0)
        {
           OnDataReceived();
        }
     }
  }

Socket对象还具有定义为以下内容的OnDataReceived()方法:

  protected void OnDataReceived()
  {
     var dataEventArgs = new DataEventArgs();
     dataEventArgs.DataBuffer = DataBuffer;

     // *** cross-thread risk here!
     DataReceived?.Invoke(this, dataEventArgs);
  }

我用“ 1”和“ 2”蓝色星标了图中的两个区域。

在“ 1”(在关系图上)中,我正在使用异步/等待模式。 我正在使用不支持异步的第三方套接字工具,因此将其包装在我自己的ReadDataAsync()函数中,如下所示:

  public override async Task ReadDataAsync()
  {
     // read a data asynchronously
     var task = Task.Run(() => ReadData());
     await task;
  }

ReadData()包装了第三方组件的read方法:它填充了全局数据缓冲区,因此不需要返回值。

在图中的“ 2”中,我遇到了OnDataReceived()方法中描述的跨线程风险。

底线:如果我如上面的第一个代码片段所示设置SynchronizationContext ,则Socket对象中的所有内容都将在其自己的线程上运行,直到尝试调用DataReceived事件处理程序为止; 如果我注释掉SynchronizationContext ,则在其自己的线程上运行的代码的唯一部分是包装在DataReadAsync()方法中的简短的第三方套接字读取操作。

因此,我的想法是是否可以在尝试调用DataReceived事件处理程序之前设置SynchronizationContext 即使我“可以”,更好的问题是这是否是一个好主意。 如果我确实在非UI线程中修改了SynchronizationContext ,则在调用DataReceived方法后必须将其设置回其原始值,这给我带来了类似榴莲的代码气味。

我的设计是否需要进行优雅的调整,还是需要进行大修? 我的目标是使图中的所有红色项目都在非UI线程上运行,而黑色项目在UI线程上运行。 “ 2”是非UI线程与UI线程的交叉点。

谢谢。

直接设置上下文不是最好的主意,因为此线程中偶尔执行的其他功能可能会受到影响。 控制async/await流的同步上下文的最自然的方法是使用ConfigureAwait 因此,在您的情况下,我看到两种选择来实现所需的目标:

1)ConfigureAwait(false)ReadDataAsync

public async void StartMessageLoopAsync()
{
    while (true)
    {
        // read socket data asynchronously and populate the global DataBuffer
        await ReadDataAsync().ConfigureAwait(false);

        if (DataBuffer.Count == 0)
        {
            OnDataReceived();
        }
    }
}

这将使在后台线程中等待之后恢复所有操作。 然后使用Dispatcher Invoke DataReceived?.Invoke Invoke UI线程:

protected void OnDataReceived()
{
   var dataEventArgs = new DataEventArgs();
   dataEventArgs.DataBuffer = DataBuffer;

   // *** cross-thread risk here!
   Dispatcher.CurrentDispathcer.Invoke(() => { DataReceived?.Invoke(this, dataEventArgs ); });
}

或者2)进行如下逻辑分解:

    public async void StartMessageLoopAsync()
    {
        while (true)
        {
            // read socket data asynchronously and populate the global DataBuffer
            await ProcessDataAsync();

            // this is going to run in UI thread but there is only DataReceived invocation
            if (DataBuffer.Count == 0)
            {
                OnDataReceived();
            }
        }
    }

OnDataReceived现在很薄,并且仅事件触发

    protected void OnDataReceived()
    {
        // *** cross-thread risk here!
        DataReceived?.Invoke(this, dataEventArgs);
    }

这巩固了应该在后台线程中运行的功能

    private async Task ProcessDataAsync()
    {
        await ReadDataAsync().ConfigureAwait(false);

        // this is going to run in background thread
        var dataEventArgs = new DataEventArgs();
        dataEventArgs.DataBuffer = DataBuffer;
    }

    public override async Task ReadDataAsync()
    {
        // read a data asynchronously
        var task = Task.Run(() => ReadData());
        await task;
    }

这似乎与被动扩展更好地配合使用:

.NET的反应性扩展

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM