繁体   English   中英

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

[英]Run async task continuation on native function callback thread

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

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

  1. 构造一个Task对象,向构造函数传递一个返回结果的lambda。
  2. 使用RunSynchronously方法构造运行此任务的回调
  3. 调用异步本机函数,传入回调
  4. 将任务对象返回给调用者

我的代码看起来像这样

/// 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之后运行的代码在与回调相同的线程上运行? 目前,我看到它在不同的线程上运行,即使我正在调用readCompletionTask.RunSynchronously 我在ASP.NET和IIS下运行此代码。
  2. 本机QueueUserWorkItem函数是否使用与托管的ThreadPool.QueueUserWorkItem方法相同的线程池 我的意见是它应该,因此托管的TaskScheduler应该可以在本机回调线程上安排任务。

您不应该在现代代码中使用Task构造函数。 完全没有。 永远。 它没有用例。

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

如何在调用await ReadAsync之后运行的代码在与回调相同的线程上运行?

你无法保证; 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;
}

本机QueueUserWorkItem函数是否使用与托管的ThreadPool.QueueUserWorkItem方法相同的线程池?

不,这是两个完全不同的线程池。

如何在调用await ReadAsync之后运行的代码在与回调相同的线程上运行?

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

另外, FromCurrentSynchronizationContext返回什么? 我的蜘蛛感觉告诉我这是基于一种误解......

本机QueueUserWorkItem函数是否使用与托管的ThreadPool.QueueUserWorkItem方法相同的线程池?

我不这么认为,即使是这种情况,你也无法定位一个特定的线程。 您只能定位特定池。

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


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


我认为你有一个GC漏洞,因为没有任何东西让asyncInfo.Callback保持活着状态。 当本地呼叫正在进行时,它可以被收集起来。 在回调中使用GC.KeepAlive

暂无
暂无

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

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