sshnet / SSH.NET

SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism.
http://sshnet.github.io/SSH.NET/
MIT License
3.88k stars 919 forks source link

Plumbing for more async/await work #1281

Closed jacobslusser closed 5 months ago

jacobslusser commented 6 months ago

Changes

Notes This PR is mostly about preparation for more async/await work and proving some of the techniques.

I made the SocketAbstraction class partial so we could put conditional methods in a separate SocketAbstraction.Async.cs file. Let me know if we like that pattern because I expect a lot more conditional code is coming for other classes.

WojciechNagorski commented 6 months ago

It looks ok but I need to test it and check a few things.

Rob-Hague commented 6 months ago

LGTM FWIW

Here's a prototype of an async lock type that encapsulates a SemaphoreSlim:

```c# // Inspired somewhat by the System.Threading.Lock proposal // (https://github.com/dotnet/runtime/issues/34812) public sealed class AsyncLock : IDisposable { private readonly SemaphoreSlim _semaphore = new(initialCount: 1, maxCount: 1); public LockScope EnterLockScope() { _semaphore.Wait(); return new LockScope(this); } public ValueTask EnterLockScopeAsync() { if (_semaphore.Wait(0)) { return ValueTask.FromResult(new LockScope(this)); } return new ValueTask(WaitAsync(this)); static async Task WaitAsync(AsyncLock @lock) { await @lock._semaphore.WaitAsync().ConfigureAwait(false); return new LockScope(@lock); } } public void Dispose() { _semaphore.Dispose(); } public struct LockScope : IDisposable { private readonly AsyncLock _lock; private bool _disposed; public LockScope(AsyncLock @lock) { _lock = @lock; Debug.Assert(_lock._semaphore.CurrentCount == 0); } public void Dispose() { if (!_disposed) { _disposed = true; Debug.Assert(_lock._semaphore.CurrentCount == 0); _lock._semaphore.Release(); } } } } ``` Example: ```c# AsyncLock @lock = new(); const int NumTasks = 10; const int NumIterations = 5; int counter = 0; Barrier barrier = new(NumTasks + 1); for (int x = 0; x < NumTasks; x++) { Task.Run(async () => { barrier.SignalAndWait(); for (int i = 0; i < NumIterations; i++) { using (await @lock.EnterLockScopeAsync().ConfigureAwait(false)) { counter++; Console.WriteLine($"{counter} ({Environment.CurrentManagedThreadId})"); } } }); } barrier.SignalAndWait(); for (int i = 0; i < NumIterations; i++) { using (@lock.EnterLockScope()) { counter++; Console.WriteLine($"{counter} (main)"); } } // 1 (11) // 2 (16) // 3 (27) // 4 (26) // 5 (17) // 6 (29) // 7 (7) // 8 (26) // 9 (17) // 10 (16) // 11 (7) // 12 (26) // 13 (16) // 14 (29) // 15 (16) // 16 (17) // 17 (26) // 18 (7) // 19 (27) // 20 (16) // 21 (7) // 22 (17) // 23 (main) // 24 (17) // 25 (27) // 26 (27) // 27 (27) // 28 (17) // 29 (17) // 30 (27) // 31 (27) // 32 (17) // 33 (10) // 34 (10) // 35 (main) // 36 (10) // 37 (27) // 38 (10) // 39 (27) // 40 (10) // 41 (27) // 42 (10) // 43 (10) // 44 (17) // 45 (17) // 46 (17) // 47 (main) // 48 (17) // 49 (27) // 50 (27) // 51 (27) // 52 (10) // 53 (10) // 54 (main) // 55 (main) ```
WojciechNagorski commented 5 months ago

LGTM! sorry for the delay.

WojciechNagorski commented 5 months ago

@jacobslusser I hope this delay hasn't discouraged you. Next time I'll try to deal with your PR right away.

jacobslusser commented 5 months ago

@jacobslusser I hope this delay hasn't discouraged you. Next time I'll try to deal with your PR right away.

All good. :smile:

WojciechNagorski commented 4 months ago

This issue has been fixed in the 2024.0.0 version.