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

C# 在本机函数回调线程上运行异步任务延续,c#,windows,async-await,pinvoke,C#,Windows,Async Await,Pinvoke,我有一个C函数FsReadStream,它执行一些异步工作并接受回调。完成后,它将使用windows函数调用回调 我试图使用异步/等待模式从托管代码(c#)调用此函数。所以我做了以下几点 构造一个Task对象,将返回结果的lambda传递给构造函数 使用RunSynchronously方法构造一个运行此任务的回调 调用异步本机函数,传入回调 将任务对象返回给调用者 我的代码看起来像这样 /// Reads into the buffer as many bytes as the buffer s

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

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

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

    /// 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);
    
    我有两个问题

  • 如何使调用
    wait ReadAsync
    后运行的代码与回调在同一线程上运行?目前,我看到它运行在不同的线程上,即使我正在调用
    readCompletionTask.RunSynchronously
    。我正在ASP.NET和IIS下运行此代码
  • 本机
    QueueUserWorkItem
    函数是否使用与托管方法相同的线程池?我的意见是应该,因此托管的
    TaskScheduler
    应该可以在本机回调线程上调度任务
  • 如何使调用wait ReadAsync后运行的代码与回调在同一线程上运行

    这是不可能以可靠的方式实现的<代码>同步执行不是保证<代码>同步运行也不能保证它。当然,您可以传入回调并同步调用该回调

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

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

    我不这么认为,即使在这种情况下,你也不能针对特定的线程。您只能针对特定的池

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


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



    我认为您有一个GC漏洞,因为没有任何东西可以保持
    asyncInfo.Callback
    活动。它可以在本机调用进行时收集。在回调中使用
    GC.KeepAlive

    在现代代码中不应使用
    任务
    构造函数。完全曾经它没有使用案例

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

    如何使调用wait ReadAsync后运行的代码与回调在同一线程上运行

    你不能保证<代码>等待就是不能这样工作。如果代码绝对必须在同一个线程上执行,那么应该直接从回调调用它

    然而,如果只是希望在同一个线程上执行,那么您不必做任何特殊的事情<代码>等待已同步使用
    执行
    标志:

    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;
    }
    
    公共任务ReadAsync(字节[]缓冲区) { var tcs=new TaskCompletionSource(); GCHandle pinnedBuffer=GCHandle.Alloc(buffer,GCHandleType.Pinned); IntPtr bytesToRead=Marshal.AllocHGlobal(sizeof(long)); Marshal.WriteInt64(bytesToRead,buffer.Length); FsAsyncInfo asyncInfo=新的FsAsyncInfo(); asyncInfo.Callback=(int状态)=> { tcs.TrySetResult(新的ReadResult { 错误代码=状态; BytesRead=(int)Marshal.ReadInt64(bytesToRead); }); pinnedBuffer.Free(); 弗里赫格洛巴元帅(拜特索雷德); }; NativeMethods.FsReadStream(pinnedBuffer.AddrOfPinnedObject(),bytesToRead,ref asyncInfo); 返回tcs.Task; } 本机QueueUserWorkItem函数是否使用与托管threadpool.QueueUserWorkItem方法相同的线程池


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

    我使用了
    任务
    选择器而不是
    任务完成源
    ,因为这允许我在运行任务时指定调度程序。计划程序的类型为
    System.Threading.Tasks.SynchronizationContextTaskScheduler
    。感谢您指出GC漏洞。尝试在回调线程上等待后执行代码,以避免线程之间的上下文切换您可以为任务指定调度程序,但不能为延续指定调度程序。运行
    返回readResult没有意义在任何特定线程上<代码>避免上下文切换
    确定,因此为继续指定
    同步执行
    。这种方法99%的时间都有效。然后,抛开时髦的TCS仿真,使用TCS。这回答了问题吗?谢谢,这澄清了一点。我仍在寻找一种在回调线程上运行延续的方法,即使这种方法在99%的时间内都有效。请同步使用TaskContinuationOptions.ExecuteSynchronously。就是这样。有什么问题吗?很有趣。是否存在不鼓励使用
    任务的msdn。您实际上是在声明
    任务
    ctor和
    任务。RunSynchronously
    方法已被弃用。@tcb:否。MSDN(以及大多数Microsoft文档)是描述性的,而不是规定性的。我在我的博客上总结了我关于和的观点。值得注意的是,在ASP.NET和IIS(其中@tcb指定为运行时环境)下,
    await ReadAsync(数据)
    不会在同一线程上继续,除非它是
    await ReadAsync(数据)。ConfigureAwait(false)
    @noserio:我相信它会在同一线程上继续。不过,我还没有测试它。@StephenCleary,不幸的是,它不会,至少在当前的
    AspNetSynchronizationContext
    实现中不会。我还是不知道他们为什么走那条路。