我有一些外部硬件的异步接口,允许读取和写入值。
让我们假设它看起来像这样:
interface IAsyncConnection
{
IReadOnlyDictionary<string,object> ReadAsync(IReadOnlyList<string> keysToRead)
void WriteAsync(IReadOnlyDictionary<string,object> values)
}
硬件和协议本身是线程安全的,所以如果我在同一时刻调用ReadAsync
和WriteAsync
,则两个方法中的一个获得锁定并首先执行,而另一个方法只需要有点长。
由于协议不支持更改通知,我已经实现了某种轮询循环:
IReadOnlyDictionary<string,object> oldValues = null;
while(true)
{
Task minimumTime = Task.Delay(100);
var newValues = await this.connection.ReadAsync(valuesToRead);
var changedValues = this.GetChangedValues(newVales, oldValues)
this.Update(changedValues); //Actually delegates ViewModel changes and updates the UI
await minimumTime; //just some sample
}
现在我遇到麻烦,如果我在调用循环在GetChangedValues
内时调用write,因为Update方法被调用旧值,这会在写入之前将UI设置回旧值(这会进一步导致撤消/重做堆栈等问题。)
现在,如果Read是同步的,我只会使用SyncRoot
属性扩展Connection,我可以锁定它。并在编写和更新的对象之间创建一些同步,以正确获取当前更改的值。
我如何使用异步方法获得类似的东西?
编辑:
为了澄清,SemaphoreSlim
SyncRoot不支持重入(如何,ofc)这意味着如果我在IAsyncConnection
中创建某种锁定并通过属性使其可用于外部,那么我无法调用Wait PollingLoop
类和IAsyncConnection
内的WaitAsync,因为这会重新输入SemaphoreSlim
。我当然可以通过Connection的WriteAsync方法获得SemaphoreSlim
并且PollingLoop
获得SemaphoreSlim
的方式实现它。但这似乎是某种神奇的(不可维护的)同步,因为真正的Connection有大约12个不同时运行的方法。
答案 0 :(得分:0)
我认为,您需要的是两个独立的任务 - 一个用于以100毫秒的间隔轮询硬件(协议),将数据(如果有的话)推送到BlockingCollection,另一个用于在新数据时从BlockingCollection读取数据实际上到了。
然后,计算从BlockingCollection读取的秒任务中的更改值。
BlockingCollection的优势在于您可以执行以下操作:
_queue = new BlockingCollection<KeyValuePair<string,object>>();
// The poll task does pure protocol-polling
// I assume that a read with no hardware data returns empty
// IEnumerable of KeyValuePair (immediately)
// You can have it async but thats because of the IO-operation, not
// Because you're waiting for data. The polltask does what you want
_pollTask = Task.Run(() =>
{
while(!token.IsCancellationRequested)
{
var newValues = this.connection.Read(valuesToRead);
foreach(var newValue in newValues)
{
// Next, I try to add new data and wait max 1 second
// if the queue is full to wait for the second task to read
if(!_queue.TryAdd(newValue, 1000, token))
{
if(!token.IsCancellationRequested)
{
// Log that queue was full
}
}
}
token.WaitHandle.WaitOne(100); // Sleeps 100ms
}
}
//
// The read task blocks forever (at TryTake) if no data exists (has been written
// by the poll-task), and executes check for changed values and updates
// the gui if, and only if, new data actually exists
_readTask = Task.Run(() =>
{
while(!token.IsCancellationRequested)
{
KeyValuePair<string, object> nextItem;
if(_queue.TryTake(out nextItem, Timeout.Infinite, token))
{
if(!token.IsCancellationRequested && this.ValueChanged(nextItem, oldValues)
{
this.Update(nextItem);
}
}
}
}