It seems to me that the task contains a contradiction: it is impossible to “not use a variable with a side-effect” to change the behavior when one of the functions is spinning in an infinite loop.
In this case, you need some shared behavior through which this function will be able to get information about which logic to use.
One solution is a sparse thread-safe variable. It can be another task, it can be any other variable that allows you to read and write yourself atomically: for example, a volatile variable of reference type.
Here is one of the task-based options:
using System; using System.Threading; using System.Threading.Tasks; class Notifier { private static Task<bool> taskThatMeansSomething = Task.FromResult(true); static async Task SendingAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { bool flag = await taskThatMeansSomething; if (flag) { Console.WriteLine("Flag is true. Doing one thing"); } else { Console.WriteLine("Flag is false. Doing one stuff"); } await Task.Delay(500, cancellationToken); } } static async Task Observable(CancellationToken cancellationToken) { bool result = false; while (!cancellationToken.IsCancellationRequested) { Console.WriteLine($"Changing the flag to {result}"); taskThatMeansSomething = Task.FromResult(result); result = !result; await Task.Delay(1000, cancellationToken); } } static void Main() { var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)); Task sendingTask = Task.Factory.StartNew( async () => await SendingAsync(cancellationTokenSource.Token), TaskCreationOptions.LongRunning); Task observeTask = Task.Factory.StartNew( async () => await Observable(cancellationTokenSource.Token), TaskCreationOptions.LongRunning); Console.WriteLine("Waiting 5 seconds..."); Task.WaitAll(sendingTask, observeTask); Console.WriteLine("Done! Press \"Enter\" to exit."); Console.ReadLine(); } }
Conclusion:
Waiting 5 seconds... Flag is true. Doing one thing Changing the flag to False Done! Press "Enter" to exit. Flag is false. Doing one stuff Flag is false. Doing one stuff Changing the flag to True Flag is true. Doing one thing Changing the flag to False Flag is false. Doing one stuff Flag is false. Doing one stuff Flag is false. Doing one stuff Changing the flag to True Flag is true. Doing one thing Flag is true. Doing one thing Changing the flag to False Flag is false. Doing one stuff
Well, there are a few moments:
- Tasks need to be run via
TaskFactory.StartNew and indicate that they are long (because they are long) - It is possible, and even desirable, to tie a graceful cancellation through the
CancellationTokenSource and CancellationToken . - It is reasonable to use the standard for C # idiom naming.
- It is reasonable to make long running methods return
Task .
Instead of the taskThatMeansSomething variable variable, taskThatMeansSomething can think in the direction of the same TPL Dataflow, when, depending on the input data changes, different blocks are launched that are responsible for sending data in one or another mode.
Here then it will be possible to fully escape from the shared state in the direction of more actor-based parallelism. If interested, then I can jot down this option too.