C# 在本机函数回调线程上运行异步任务延续
我有一个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
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
应该可以在本机回调线程上调度任务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
实现中不会。我还是不知道他们为什么走那条路。