我有一个C函数FsReadStream
,它执行一些异步工作并进行回调。完成后,它使用QueueUserWorkItem窗口函数调用回调。
我试图使用async / await模式从托管代码(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);
我有两个问题
await ReadAsync
后运行的代码在与回调相同的线程上运行?目前,我看到它在不同的线程上运行,即使我正在调用readCompletionTask.RunSynchronously
。我在ASP.NET和IIS下运行此代码。QueueUserWorkItem
函数是否使用与托管ThreadPool.QueueUserWorkItem方法相同的线程池?我的意见是它应该,因此托管TaskScheduler
应该可以在本机回调线程上安排任务。答案 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
。