运行 本机函数回调线程上的异步任务继续

Run async task continuation on native function callback thread

我有一个 C 函数 FsReadStream,它执行一些异步工作并接受回调。完成后,它使用 QueueUserWorkItem windows 函数调用回调。

我正在尝试使用 async/await 模式从托管代码 (c#) 调用此函数。所以我做了以下

  1. 构造一个 Task 对象,向构造函数传递一个 returns 结果的 lambda。
  2. 使用 RunSynchronously 方法
  3. 构造一个 运行 执行此任务的回调
  4. 调用异步原生函数,传入回调
  5. Return调用者的任务对象

我的代码看起来像这样

/// Reads into the buffer as many bytes as the buffer size
public Task<ReadResult> ReadAsync(byte[] buffer)
{
    GCHandle pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
    IntPtr bytesToRead = Marshal.AllocHGlobal(sizeof(long));
    Marshal.WriteInt64(bytesToRead, buffer.Length);

    FsAsyncInfo asyncInfo = new FsAsyncInfo();
    ReadResult readResult = new ReadResult();

    Task<ReadResult> readCompletionTask = new Task<ReadResult>(() => { return readResult; });
    TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();

    asyncInfo.Callback = (int status) =>
    {
        readResult.ErrorCode = status;
        readResult.BytesRead = (int)Marshal.ReadInt64(bytesToRead);
        readCompletionTask.RunSynchronously(scheduler);
        pinnedBuffer.Free();
        Marshal.FreeHGlobal(bytesToRead);
    };

    // Call asynchronous native method    
    NativeMethods.FsReadStream(
                    pinnedBuffer.AddrOfPinnedObject(),
                    bytesToRead,
                    ref asyncInfo);

    return readCompletionTask;
}

我这样称呼它

ReadResult readResult = await ReadAsync(data);

我有两个问题

  1. 如何在调用 await ReadAsync 运行 之后在与回调相同的线程上生成 运行 的代码?目前,我在另一个线程上看到它 运行s,即使我正在调用 readCompletionTask.RunSynchronously。我在 ASP.NET 和 IIS 下 运行ning 这段代码。
  2. 本机 QueueUserWorkItem 函数是否使用与托管 ThreadPool.QueueUserWorkItem 方法相同的线程池?我的意见是它应该,因此托管 TaskScheduler 应该可以在本机回调线程上安排任务。

How to make the code that runs after the call to await ReadAsync run on the same thread as the callback?

这不可能以可靠的方式进行。 ExecuteSynchronously 不是保证。 RunSynchronously 也不保证。您当然可以传入回调并同步调用该回调。

此外,FromCurrentSynchronizationContext return 是什么?我的蜘蛛直觉告诉我这是一个误会...

Does the native QueueUserWorkItem function use the same threadpool as the managed ThreadPool.QueueUserWorkItem method?

我不这么认为,即使是这种情况,您也无法针对特定线程。您只能针对特定池。

为什么要在同一个线程上执行?通常,问这个问题的人确实想要并且需要别的东西。


您创建和return任务的方式很奇怪。为什么不使用基于 TaskCompletionSource 的标准模式?


我认为你有一个 GC 漏洞,因为没有什么能让 asyncInfo.Callback 存活。它可以在本机调用正在进行时被收集起来。在回调中使用 GC.KeepAlive

您不应在现代代码中使用 Task 构造函数。完全没有。曾经。没有用例。

在这种情况下,您应该使用 TaskCompletionSource<T>

How to make the code that runs after the call to await ReadAsync run on the same thread as the callback?

你不能保证; await 只是行不通。如果代码绝对必须在同一个线程上执行,那么它应该直接从回调中调用。

但是,如果只是首选在同一个线程上执行,那么您不必做任何特别的事情; await 已经使用了 ExecuteSynchronously 标志:

public Task<ReadResult> ReadAsync(byte[] buffer)
{
  var tcs = new TaskCompletionSource<ReadResult>();
  GCHandle pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);

  IntPtr bytesToRead = Marshal.AllocHGlobal(sizeof(long));
  Marshal.WriteInt64(bytesToRead, buffer.Length);

  FsAsyncInfo asyncInfo = new FsAsyncInfo();
  asyncInfo.Callback = (int status) =>
  {
    tcs.TrySetResult(new ReadResult
    {
      ErrorCode = status;
      BytesRead = (int)Marshal.ReadInt64(bytesToRead);
    });
    pinnedBuffer.Free();
    Marshal.FreeHGlobal(bytesToRead);
  };

  NativeMethods.FsReadStream(pinnedBuffer.AddrOfPinnedObject(), bytesToRead, ref asyncInfo);

  return tcs.Task;
}

Does the native QueueUserWorkItem function use the same threadpool as the managed ThreadPool.QueueUserWorkItem method?

没有。那是两个完全不同的线程池。