简体   繁体   English

如何包装异步/等待的多线程回调?

[英]How to wrap a multithreaded callback for async/await?

I have async/await code and want to use an API similar to a websocket. 我有一个异步/等待代码,想使用类似于websocket的API。 It takes a callback for receiving new messages which is called from another thread. 它需要一个回调来接收从另一个线程调用的新消息。

Can I execute this callback in the same async/await context as the connection initiation without resorting to locking? 是否可以在与连接启动相同的异步/等待上下文中执行此回调,而无需诉诸锁定?

I think this is what SynchronizationContext is for but I can't tell if its threadsafe. 我认为这就是SynchronizationContext的作用,但是我无法确定它的线程安全性。 If I log the thread-id, each callback will be on a different thread. 如果我记录线程ID,则每个回调将位于不同的线程上。 If I log Task.CurrentId its null. 如果我登录Task.CurrentId,则为null。 I think the same synchronisation context moves across different threads so this might be ok but I don't know how to confirm it. 我认为相同的同步上下文跨不同的线程移动,因此这可能没问题,但我不知道如何确认。

// External api, the callbacks will be from multiple threads
public class Api
{
    public static Connect(
        Action<Connection> onConnect,
        Action<Connection> onMessage) 
    {}
}

async Task<Connection> ConnectAsync(Action<Message> callback)
{
    if (SynchronizationContext.Current == null)
    {
        SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
    }

    var syncContext = SynchronizationContext.Current;

    var tcs = new TaskCompletionSource<Connection>();

    // use post() to ensure callbacks from other threads are executed thread-safely

    Action<Connection> onConnect = conn => 
    {
        syncContext.Post(o => tcs.SetResult(conn), null);
    };
    Action<Message> onMsg = msg => 
    { 
        syncContext.Post(o => callback(msg), null);
    };

    // call the multi-threaded non async/await api supplying the callbacks

    Api.Connect(onConnect, onMsg);

    return await tcs.Task;
}

var connection = await ConnectAsync(
    msg => 
    { 
        /* is code here threadsafe with the send without extra locking? */ 
    });

await connection.Send("Hello world);

Thanks to @Evk who pointed out that the default SynchronizationContext doesn't actually synchronise anything or implement send/post in the way you would expect. 感谢@Evk,他指出默认的SynchronizationContext实际上并没有以您期望的方式同步任何内容或实现发送/发布。

https://github.com/StephenClearyArchive/AsyncEx.Context https://github.com/StephenClearyArchive/AsyncEx.Context

The fix is to use Stephen Cleary's async library which implements a SynchronizationContext as a message pump in a single thread so that the post() calls are called in the same thread as the other awaited calls. 解决方法是使用Stephen Cleary的异步库,该库在单个线程中实现SynchronizationContext作为消息泵,以便与其他等待的调用在同一线程中调用post()调用。

// External api, the callbacks will be from multiple threads
public class Api
{
    public static Connect(
        Action<Connection> onConnect,
        Action<Connection> onMessage) 
    {}
}

async Task<Connection> ConnectAsync(Action<Message> callback)
{
    var syncContext = SynchronizationContext.Current;

    var tcs = new TaskCompletionSource<Connection>();

    // use post() to ensure callbacks from other threads are executed thread-safely

    Action<Connection> onConnect = conn => 
    {
        syncContext.Post(o => tcs.SetResult(conn), null);
    };
    Action<Message> onMsg = msg => 
    { 
        syncContext.Post(o => callback(msg), null);
    };

    // call the multi-threaded non async/await api supplying the callbacks

    Api.Connect(onConnect, onMsg);

    return await tcs.Task;
}

//
// https://github.com/StephenClearyArchive/AsyncEx.Context
//
Nito.AsyncEx.AsyncContext.Run(async () =>
{
    var connection = await ConnectAsync(
        msg => 
        { 
            /* this will execute in same thread as ConnectAsync and Send */ 
        });

    await connection.Send("Hello world);

    ... more async/await code
});

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

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