2014-01-09 16 views
12

Ich arbeite an einer C# API, die von einer Vielzahl von Verbrauchern verwendet wird. Diese API bietet Zugriff auf eine freigegebene Ressource (in meinem Fall Hardware, die serielle Kommunikation durchführt), die oft mehrere verschiedene Akteure haben, die versuchen, sie gleichzeitig zu verwenden.Wie können Ressourcen geschützt werden, die in einer Multithread- oder asynchronen Umgebung verwendet werden können?

Das Problem, das ich habe, ist, dass einige meiner Kunden dies in einer Multi-Threaded-Umgebung verwenden möchten - jeder Akteur arbeitet unabhängig und versucht, die Ressource zu verwenden. Ein einfaches Schloss funktioniert hier gut. Einige meiner Kunden würden es jedoch vorziehen, async-await zu verwenden und die Ressource in Zeitscheiben zu zerlegen. (Wie ich es verstehe) erfordert dies eine asynchrone Sperre, um die Zeitscheibe zurück zu anderen Aufgaben zu bringen; Blockieren an einer Sperre würde diesen ganzen Thread stoppen.

Und ich stelle mir vor, dass serielle Sperren im besten Fall unperformant sind, und eine potentielle Race Condition oder schlimmstenfalls ein Deadlock.

Wie kann ich diese gemeinsam genutzte Ressource in einer gemeinsamen Codebasis für beide mögliche Nebenkonsumnutzungen schützen?

Antwort

24

Sie können SemaphoreSlim mit 1 als Anzahl der Anfragen verwenden. SemaphoreSlim ermöglicht sowohl eine async Art und Weise zu verriegeln mit WaitAsync und die alte synchrone Art und Weise:

await _semphore.WaitAsync() 
try 
{ 
    ... use shared resource. 
} 
finally 
{ 
    _semphore.Release() 
} 

Sie können auch Ihre eigenen AsyncLock basiert auf Stephen Toub der großen Building Async Coordination Primitives, Part 6: AsyncLock Beitrag schreiben. Ich habe es in meiner Anwendung getan und synchrone und asynchrone Sperren für dasselbe Konstrukt zugelassen.

Verbrauch:

// Async 
using (await _asyncLock.LockAsync()) 
{ 
    ... use shared resource. 
} 

// Synchronous 
using (_asyncLock.Lock()) 
{ 
    ... use shared resource. 
} 

Umsetzung:

class AsyncLock 
{ 
    private readonly Task<IDisposable> _releaserTask; 
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); 
    private readonly IDisposable _releaser; 

    public AsyncLock() 
    { 
     _releaser = new Releaser(_semaphore); 
     _releaserTask = Task.FromResult(_releaser); 
    } 
    public IDisposable Lock() 
    { 
     _semaphore.Wait(); 
     return _releaser; 
    } 
    public Task<IDisposable> LockAsync() 
    { 
     var waitTask = _semaphore.WaitAsync(); 
     return waitTask.IsCompleted 
      ? _releaserTask 
      : waitTask.ContinueWith(
       (_, releaser) => (IDisposable) releaser, 
       _releaser, 
       CancellationToken.None, 
       TaskContinuationOptions.ExecuteSynchronously, 
       TaskScheduler.Default); 
    } 
    private class Releaser : IDisposable 
    { 
     private readonly SemaphoreSlim _semaphore; 
     public Releaser(SemaphoreSlim semaphore) 
     { 
      _semaphore = semaphore; 
     } 
     public void Dispose() 
     { 
      _semaphore.Release(); 
     } 
    } 
} 
+0

Dies sieht nicht meine Frage zu lösen. Dies stellt den gleichen Mechanismus zur Verfügung, würde aber meine API zwingen, zwischen asynchronen und synchronen Sperren zu wählen - wenn sie nicht wissen kann, welcher der beiden sie aufruft. Wenn ich nicht falsch verstehe? – Telastyn

+1

@Telastyn: Ihre API-Methoden sind entweder synchron oder asynchron. Wenn sie synchron sind, verwenden Sie 'Wait'; Wenn sie asynchron sind, verwenden Sie 'WaitAsync'. –

+0

@StephenCleary - die freigegebene Ressource ist ~ 4 Schichten tief unter der Haube. An diesem Punkt im Code weiß es nicht (und sollte nicht wissen), ob es asynchron aufgerufen wird oder nicht. Einen parallelen Pfad durch die Codebasis zu führen ist nicht nur unpraktisch, sondern scheint auch unnötig zu sein. Das scheint sicherlich ein Problem zu sein, auf das andere gestoßen sind. – Telastyn

Verwandte Themen