在本机函数回调线程

时间:2015-10-26 18:53:16

标签: c# windows async-await pinvoke

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

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

  1. 构造一个Task对象,传递构造函数一个返回结果的lambda。
  2. 使用RunSynchronously方法
  3. 构建运行此任务的回调
  4. 调用异步本机函数,传入回调
  5. 将任务对象返回给调用者
  6. 我的代码看起来像这样

    /// 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应该可以在本机回调线程上安排任务。

2 个答案:

答案 0 :(得分:3)

您不应该在现代代码中使用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方法相同的线程池?

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

答案 1 :(得分:2)

  

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

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

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

  

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

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

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

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

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