C# 在异步方法中更改SynchronizationContext

C# 在异步方法中更改SynchronizationContext,c#,multithreading,async-await,C#,Multithreading,Async Await,我试图在这里发布一个没有代码的精简问题,因为我的问题非常具体:在异步方法中修改同步上下文是否可能/可以接受?如果在异步方法开始时未设置SynchronizationContext,则其中的代码(包括我引发的事件和我调用的同一类模块中的方法)似乎在同一工作线程上运行。但是,当需要与UI交互时,我发现必须将同步上下文设置为UI线程 在我想调用基于UI的函数之前,可以将同步上下文设置为工作线程吗 编辑: 为了进一步澄清我的上述问题,我发现自己在SynchronizationContext设置方面处于一

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

在我想调用基于UI的函数之前,可以将同步上下文设置为工作线程吗

编辑: 为了进一步澄清我的上述问题,我发现自己在SynchronizationContext设置方面处于一种不可能获胜的局面。如果不设置SynchronizationContext,那么我的异步操作将在单独的线程上运行(根据需要),但是如果不遇到跨线程操作异常,我无法将数据返回到UI线程;如果我将SynchronizationContext设置为我的UI线程,那么我希望在单独的线程上运行的操作最终会在UI线程上运行——然后(当然)我避免了跨线程异常,一切正常。很明显,我遗漏了一些东西

如果你想多读一些,我已经试着给你一个非常清晰的解释,我知道你在花时间去理解,所以谢谢你

我试图做的事情如流程图所示:

我有一个Winforms应用程序在UI线程上运行(黑色);我有一个套接字对象,我想在它自己的线程上运行。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();
}
  public override async Task ReadDataAsync()
  {
     // read a data asynchronously
     var task = Task.Run(() => ReadData());
     await task;
  }
当我从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()
函数中,如下所示:

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();
}
  public override async Task ReadDataAsync()
  {
     // read a data asynchronously
     var task = Task.Run(() => ReadData());
     await task;
  }
ReadData()
包装第三方组件的读取方法:它填充全局数据缓冲区,因此不需要返回值

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

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

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

我的设计是否有优雅的调整,还是需要大修?我的目标是让图表中的所有红色项目在非UI线程上运行,黑色项目在UI线程上运行。“2”是非UI线程my转换到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
进入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;
    }

任何事情都是可能的,不过这个问题似乎有点。。好。。犯罪嫌疑人有很多机制可以发回UI。最简单的是异步等待模式,并允许它从UI传播,尽管还有更多的模式取决于您正在做什么和如何做。这里的实际用例是什么?是的,我认为需要更多的信息。我将发布一个更完整的问题,因为我真正想做的是阻止异步方法返回到UI线程,直到我特别希望它返回。当我在异步方法调用之前设置SynchronizationContext时,当我调用某些成员时,异步方法会返回UI线程,我不希望它这样做。显然,我需要重新思考设计。谢谢你的参与+1O