如何将以下回调驱动代码转换为异步/等待模式:
public class DeviceWrapper
{
// external device which provides real time stream of data
private InternalDevice device = new InternalDevice();
private List<int> accumulationBuffer = new List<int>();
public void StartReceiving()
{
// the following callback invocations might by synchronized by main
// UI message pump, particular window message pump
// or some other way
device.Synchronization = Synchronization.UI;
device.DataAvailable += DataAvailableHandler;
device.ReceivingStoppedOrErrorOccured += StopHandler;
device.Start();
}
private void DataAvailableHandler(object sender, DataEventArgs e)
{
// Filter data from e.Data and accumulate to accumulationBuffer field.
// If certail condition is met, signal pending task (if there is any)
//as complete return to the awaiting caller accumulationBuffer or perhaps temporary buffer created from accumulationBuffer
// in order to make it available to the caller.
// Handle also requested cancellation.
}
public Task<byte[]> GetData(CancellationToken token)
{
// create task returning data filtered and accumulated in DataAvailableHandler
}
}
// usage:
async void Test()
{
DeviceWrapper w = new DeviceWrapper();
w.StartReceiving();
while(true)
{
byte[] filteredData = await w.GetData(CancellationToken.Null);
Use(filteredData);
}
}
我通过阅读.NET StreamReader类源寻求灵感来解决这个问题,但这让我更加困惑。
感谢专家的任何建议!
答案 0 :(得分:3)
您正在寻找TaskCompletionSource<byte[]>
。这是它看起来的近似值:
public Task<byte[]> GetData(CancellationToken token)
{
cancellationToken.ThrowIfCancellationRequested;
var tcs = new TaskCompletionSource<byte[]>();
DataEventHandler dataHandler = null;
dataHandler = (o, e) =>
{
device.DataAvailable -= dataHandler;
tcs.SetResult(e.Data);
}
StopEventHandler stopHandler = null;
stopHandler = (os, se) =>
{
device.ReceivingStoppedOrErrorOccured -= stopHandler;
// Assuming stop handler has some sort of error property.
tcs.SetException(se.Exception);
}
device.DataAvailable += dataHandler;
device.ReceivingStoppedOrErrorOccured += stopHandler;
device.Start();
return tcs.Task;
}
答案 1 :(得分:1)
如果您正确使用异步等待,您的代码会更容易:
首先:
<TResult
&gt;而不是TResult 现在实施你的例子。有几种方法可以解决这个问题,但我认为这通常是一个生产者 - 消费者模式:我们有一个对象,它以与另一个消耗它们的对象无关的速度生成事物。
您可以自己创建,使用信号量来发送新数据,但.NET已经有了这方面的内容:
System.Threading.Tasks.DataFlow.BufferBlock。
您需要下载微软nuget软件包。请参阅BufferBlock的MSDN描述中的备注。
BufferBlock是您发送类型为T的对象,而另一个任务则等待T类型的对象到达。完全支持async / await。
发件人方:
<T
&gt;其中T是它发送的类型。<T
&gt;将发件人作为单独的对象。作为财产。消费者方:
<T
&GT;实现als ISourceBlock <T
&gt; 好的,让我们把它们放在一起:
public class DeviceWrapper
{
// external device which provides real time stream of data
private InternalDevice device = new InternalDevice();
// internal buffer replaced by the bufferBlock
BufferBlock<byte> bufferBlock = new BufferBlock<byte>()
public void StartReceiving() {...}
private async void DataAvailableHandler(object sender, DataEventArgs e)
{
// get the input and convert it to a byte
// post the byte to the buffer block asynchronously
byte byteToSend = ...
await this.bufferBlock.SendAsync(byteToSend);
}
public async Task<IEnumerable<byte>> GetData(CancellationToken token)
{
List<byte> receivedBytes = new List<byte>()
while (await this.BufferBlock.OutputAvailableAsync(token))
{ // a byte is available
byte b = await this.bufferBlock.ReceiveAsync(token);
receivedBytes.Add(b);
if (receivedBytes.Count > ...)
{
return receivedBytes;
}
// else: not enough bytes received yet, wait for more
}
}
}
async Task Test(CancellationToken token)
{
DeviceWrapper w = new DeviceWrapper();
w.StartReceiving();
while(NoStopRequested)
{
token.ThrowIfCancellationrequested();
var filteredData = await w.GetData(token);
Use(filteredData);
}
}
还有很多东西可以告诉BufferBlocks,特别是如果没有数据可用,如何整齐地停止它们MSDN有几个例子。 请参阅并行库中的DataFlow一章
https://msdn.microsoft.com/en-us/library/hh228603(v=vs.110).aspx