0

I have some variable which is written by one thread and read by another (or several) thread. I want the read threads to be able to wait for the variable to become a certain value without constantly polling it. How can I do this in a threadsafe way? My initial idea was: Read Thread:

  1. check the variable for the correct value inside a lock. if correct -> do something, if not -> continue.
  2. wait on AutoResetEvent signal, then check for the correct value inside a lock. if correct -> do something, if not -> redo this step

Write thread:

  1. write the new variable value inside a lock
  2. signal AutoResetEvent that the value has changed

If the read thread checks the value and it is not correct, the write thread may signal the AutoResetEvent before the read thread begins waiting. This means the read thread will keep waiting forever(or until the value changes again). Is there some pattern which solves this issue?

Claude Hasler
  • 396
  • 1
  • 14
  • Seems like a typical example where Monitor class can be used: https://stackoverflow.com/questions/1559293/c-sharp-monitor-wait-pulse-pulseall – vhr Aug 19 '22 at 07:46
  • 1
    An [`AutoResetEvent`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.autoresetevent?view=net-6.0): "resets automatically after releasing a single waiting thread. ... If no threads are waiting, the state remains signaled indefinitely.". It doesn't need the thread to *already be waiting*, so your perceived problem doesn't exist. There's no "lost wake up" problem using the event objects. – Damien_The_Unbeliever Aug 19 '22 at 07:58
  • @vhr Something like this?: https://dotnetfiddle.net/3vwkm5 – Claude Hasler Aug 19 '22 at 08:10
  • @Damien_The_Unbeliever you are right of course! I can just lock, check the condition, leave the lock, then wait to be signaled. This would also work with a a SemaphoreSlim, with the added benefit of being async capable as well as having a CancellationToken and timeout. – Claude Hasler Aug 19 '22 at 10:55

1 Answers1

0

Although signaling constructions (like Monitor.Wait, AutoResestEvent etc.) are quite obvious here, I suggest to look into channels. So instead of checking the value you can read and check a stream of values instead:

[Fact]
public async Task Basic()
{
    var channel = Channel.CreateUnbounded<int>();
    int readCount = 0;
    int finalValue = 0;
    
    var reader = Task.Run(async () =>
    {
        while (true)
        {
            try
            {
                var item = await channel.Reader.ReadAsync(CancellationToken.None);
                Interlocked.Exchange(ref finalValue, item);
                Interlocked.Increment(ref readCount);
                
                // you can check item's value here
            }
            catch (ChannelClosedException)
            {
                break;
            }
        }
    });

    var writer = Task.Run(async () =>
    {
        await channel.Writer.WriteAsync(15);
        await Task.Delay(500);
        await channel.Writer.WriteAsync(244);
        await Task.Delay(500);
        await channel.Writer.WriteAsync(311);
        await Task.Delay(500);
        await channel.Writer.WriteAsync(466);
        channel.Writer.Complete();
    });
    await Task.WhenAll(reader, writer);

    readCount.Should().Be(4);
    finalValue.Should().Be(466);
}
mtkachenko
  • 5,389
  • 9
  • 38
  • 68
  • This will allow me to react to changes in a value, but will the read thread also receive the "last" state of the variable at at the first readAsync() call? Or will a ReadAsync() wait until the next WriteAsync()? – Claude Hasler Aug 19 '22 at 08:14
  • The consuming `while` loop could be simplified to: `await foreach (var item in channel.Reader.ReadAllAsync()) { /*...*/ }` – Theodor Zoulias Aug 19 '22 at 08:16
  • This one `ReadAsync() will wait until the next WriteAsync()`. You can write a logic to store current value after each iteration in the read-loop. Anyway if you want the reader get the current value first you should store the value somewhere. So maybe it's easier to use waithandles. FIY there are async versions of waithandles in the nuget package https://www.nuget.org/packages/Microsoft.VisualStudio.Threading/ – mtkachenko Aug 19 '22 at 08:46