使用异步函数同步数据

时间:2016-04-26 11:18:13

标签: c# synchronization locking semaphore

我有一些外部硬件的异步接口,允许读取和写入值。

让我们假设它看起来像这样:

interface IAsyncConnection
{
    IReadOnlyDictionary<string,object> ReadAsync(IReadOnlyList<string> keysToRead)
    void WriteAsync(IReadOnlyDictionary<string,object> values) 
}

硬件和协议本身是线程安全的,所以如果我在同一时刻调用ReadAsyncWriteAsync,则两个方法中的一个获得锁定并首先执行,而另一个方法只需要有点长。

由于协议不支持更改通知,我已经实现了某种轮询循环:

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个不同时运行的方法。

1 个答案:

答案 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);
            }
        }
    }
}