C# Wait until condition is true
Solution 1
At least you can change your loop from a busy-wait to a slow poll. For example:
while (!isExcelInteractive())
{
Console.WriteLine("Excel is busy");
await Task.Delay(25);
}
Solution 2
Ended up writing this today and seems to be ok. Your usage could be:
await TaskEx.WaitUntil(isExcelInteractive);
code (including the inverse operation)
public static class TaskEx
{
/// <summary>
/// Blocks while condition is true or timeout occurs.
/// </summary>
/// <param name="condition">The condition that will perpetuate the block.</param>
/// <param name="frequency">The frequency at which the condition will be check, in milliseconds.</param>
/// <param name="timeout">Timeout in milliseconds.</param>
/// <exception cref="TimeoutException"></exception>
/// <returns></returns>
public static async Task WaitWhile(Func<bool> condition, int frequency = 25, int timeout = -1)
{
var waitTask = Task.Run(async () =>
{
while (condition()) await Task.Delay(frequency);
});
if(waitTask != await Task.WhenAny(waitTask, Task.Delay(timeout)))
throw new TimeoutException();
}
/// <summary>
/// Blocks until condition is true or timeout occurs.
/// </summary>
/// <param name="condition">The break condition.</param>
/// <param name="frequency">The frequency at which the condition will be checked.</param>
/// <param name="timeout">The timeout in milliseconds.</param>
/// <returns></returns>
public static async Task WaitUntil(Func<bool> condition, int frequency = 25, int timeout = -1)
{
var waitTask = Task.Run(async () =>
{
while (!condition()) await Task.Delay(frequency);
});
if (waitTask != await Task.WhenAny(waitTask,
Task.Delay(timeout)))
throw new TimeoutException();
}
}
Example usage: https://dotnetfiddle.net/Vy8GbV
Solution 3
You can use thread waiting handler
private readonly System.Threading.EventWaitHandle waitHandle = new System.Threading.AutoResetEvent(false);
private void btnOk_Click(object sender, EventArgs e)
{
// Do some work
Task<string> task = Task.Run(() => GreatBigMethod());
string GreatBigMethod = await task;
// Wait until condition is false
waitHandle.WaitOne();
Console.WriteLine("Excel is busy");
waitHandle.Reset();
// Do work
Console.WriteLine("YAY");
}
then some other job need to set your handler
void isExcelInteractive()
{
/// Do your check
waitHandle.Set()
}
Update: If you want use this solution, you have to call isExcelInteractive() continuously with specific interval:
var actions = new []{isExcelInteractive, () => Thread.Sleep(25)};
foreach (var action in actions)
{
action();
}
Solution 4
This implementation is totally based on Sinaesthetic's, but adding CancellationToken and keeping the same execution thread and context; that is, delegating the use of Task.Run() up to the caller depending on whether condition needs to be evaluated in the same thread or not.
Also, notice that, if you don't really need to throw a TimeoutException and breaking the loop is enough, you might want to make use of cts.CancelAfter() or new CancellationTokenSource(millisecondsDelay) instead of using timeoutTask with Task.Delay plus Task.WhenAny.
public static class AsyncUtils
{
/// <summary>
/// Blocks while condition is true or task is canceled.
/// </summary>
/// <param name="ct">
/// Cancellation token.
/// </param>
/// <param name="condition">
/// The condition that will perpetuate the block.
/// </param>
/// <param name="pollDelay">
/// The delay at which the condition will be polled, in milliseconds.
/// </param>
/// <returns>
/// <see cref="Task" />.
/// </returns>
public static async Task WaitWhileAsync(CancellationToken ct, Func<bool> condition, int pollDelay = 25)
{
try
{
while (condition())
{
await Task.Delay(pollDelay, ct).ConfigureAwait(true);
}
}
catch (TaskCanceledException)
{
// ignore: Task.Delay throws this exception when ct.IsCancellationRequested = true
// In this case, we only want to stop polling and finish this async Task.
}
}
/// <summary>
/// Blocks until condition is true or task is canceled.
/// </summary>
/// <param name="ct">
/// Cancellation token.
/// </param>
/// <param name="condition">
/// The condition that will perpetuate the block.
/// </param>
/// <param name="pollDelay">
/// The delay at which the condition will be polled, in milliseconds.
/// </param>
/// <returns>
/// <see cref="Task" />.
/// </returns>
public static async Task WaitUntilAsync(CancellationToken ct, Func<bool> condition, int pollDelay = 25)
{
try
{
while (!condition())
{
await Task.Delay(pollDelay, ct).ConfigureAwait(true);
}
}
catch (TaskCanceledException)
{
// ignore: Task.Delay throws this exception when ct.IsCancellationRequested = true
// In this case, we only want to stop polling and finish this async Task.
}
}
/// <summary>
/// Blocks while condition is true or timeout occurs.
/// </summary>
/// <param name="ct">
/// The cancellation token.
/// </param>
/// <param name="condition">
/// The condition that will perpetuate the block.
/// </param>
/// <param name="pollDelay">
/// The delay at which the condition will be polled, in milliseconds.
/// </param>
/// <param name="timeout">
/// Timeout in milliseconds.
/// </param>
/// <exception cref="TimeoutException">
/// Thrown after timeout milliseconds
/// </exception>
/// <returns>
/// <see cref="Task" />.
/// </returns>
public static async Task WaitWhileAsync(CancellationToken ct, Func<bool> condition, int pollDelay, int timeout)
{
if (ct.IsCancellationRequested)
{
return;
}
using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct))
{
Task waitTask = WaitWhileAsync(cts.Token, condition, pollDelay);
Task timeoutTask = Task.Delay(timeout, cts.Token);
Task finishedTask = await Task.WhenAny(waitTask, timeoutTask).ConfigureAwait(true);
if (!ct.IsCancellationRequested)
{
cts.Cancel(); // Cancel unfinished task
await finishedTask.ConfigureAwait(true); // Propagate exceptions
if (finishedTask == timeoutTask)
{
throw new TimeoutException();
}
}
}
}
/// <summary>
/// Blocks until condition is true or timeout occurs.
/// </summary>
/// <param name="ct">
/// Cancellation token
/// </param>
/// <param name="condition">
/// The condition that will perpetuate the block.
/// </param>
/// <param name="pollDelay">
/// The delay at which the condition will be polled, in milliseconds.
/// </param>
/// <param name="timeout">
/// Timeout in milliseconds.
/// </param>
/// <exception cref="TimeoutException">
/// Thrown after timeout milliseconds
/// </exception>
/// <returns>
/// <see cref="Task" />.
/// </returns>
public static async Task WaitUntilAsync(CancellationToken ct, Func<bool> condition, int pollDelay, int timeout)
{
if (ct.IsCancellationRequested)
{
return;
}
using (CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct))
{
Task waitTask = WaitUntilAsync(cts.Token, condition, pollDelay);
Task timeoutTask = Task.Delay(timeout, cts.Token);
Task finishedTask = await Task.WhenAny(waitTask, timeoutTask).ConfigureAwait(true);
if (!ct.IsCancellationRequested)
{
cts.Cancel(); // Cancel unfinished task
await finishedTask.ConfigureAwait(true); // Propagate exceptions
if (finishedTask == timeoutTask)
{
throw new TimeoutException();
}
}
}
}
}
Solution 5
you can use SpinUntil which is buildin in the .net-framework. Please note: This method causes high cpu-workload.
Keylee
Updated on January 14, 2021Comments
-
Keylee over 2 yearsI am trying to write a code that executes when a condition is met. Currently, I am using while...loop, which I know is not very efficient. I am also looking at AutoResetEvent() but i don't know how to implement it such that it keeps checking until the condition is true.
The code also happens to live inside an async method, so may be some kind of await could work?
private async void btnOk_Click(object sender, EventArgs e) { // Do some work Task<string> task = Task.Run(() => GreatBigMethod()); string GreatBigMethod = await task; // Wait until condition is false while (!isExcelInteractive()) { Console.WriteLine("Excel is busy"); } // Do work Console.WriteLine("YAY"); } private bool isExcelInteractive() { try { Globals.ThisWorkbook.Application.Interactive = Globals.ThisWorkbook.Application.Interactive; return true; // Excel is free } catch { return false; // Excel will throw an exception, meaning its busy } }I need to find a way to keep checking
isExcelInteractive()without CPU stuck in a loop.Note: There is no event handler in Excel that would be raised when it is not in edit mode.
-
Peyman about 8 yearsIs it your answer? Whether isExcelInteractive access to waitHandle, if not you need to share this object between both methos -
Keylee about 8 yearsI am not sure this answer would work. What is callingisExcelInteractive()? This doesnt seem checkisExcelInteractive()continuously -
Keylee about 8 yearsI did thought about this. Is this a common practice? I am still very new to programming. -
Keylee about 8 yearsPlease do, that will help me understand quicker :) -
Peyman about 8 yearsso you have to use @Ben solution, or call isExcelInteractive() method with specific interval -
Keylee about 8 yearsI like his implementation too. Very simple. -
Peyman about 8 yearsYes it is. If you need only in one place you can use his solution, otherwise you can use my (complicated) solution :) -
Keylee about 8 yearsThank you for your input. I will keep this in my pocket of tools :) -
Umair Jameel almost 7 yearsError: Can't use GreatBigMethod before it is declared. How to resolve it? -
ArieDov over 4 yearsSeems like it never throws the exception. -
Oğuzhan Soykan over 4 years@ArieDov, it depends what you want tho. If it is foreseeable thatpredicatethrows an exception then yes you need to catch and set as atcsresult -
NSFW almost 4 yearsWhile it's true that SpinUntil is a direct replacement for the while loop, it is nothing more than a direct replacement for the while loop. While it is waiting, it is holding on to the thread and it is keeping the CPU busy. Async programming was intended to avoid both of those things. A slightly more elegant solution would still block the thread but would use no CPU cycles until the condition is satisfied. A much more elegant solution would release the thread until the condition is satisfied. See the other answers on this page for examples. -
Ustin almost 4 yearscould you please share how should "Func<bool> condition" looks ? Thanks in advance, cause you have one of the most beautiful approaches. -
Sinaesthetic almost 4 years@Ustin I added a link to a fiddle with example usage ofWaitUntil(...)as an inline Func<bool> and another example as a function pointer of the same signature. It would be the same forWaitWhile(...). Basically use any function that takes no parameters and returns a bool. That function will be run on the specified interval as many times as it takes for it to evaluate to true or until it times out. -
ygoe over 3 yearsThis will still keep the CPU busy, just in a different thread while the original thread is free. What is won here? -
fibriZo raZiel about 3 yearsA couple of caveats: 1. Shouldn't it bewhile( !condition() )(negated) ? 2. Why do you use async for your threaded polling instead ofTask.Run( () => { while (!condition()) { Thread.Sleep(pollDelay); } });3. I would add a cancellation tokenwhile (!condition() && !ct.IsCancellationRequested)andTask.Delay(timeout, ct);4. I would also rename the method toWaitWhileAsyncandWaitUntilAsync. 5. Finally, I think you should delgate the choice of using a new thread withTask.Runand maybe use instead a non-threaded polling async method such asawait PollConditionAsync(). -
fibriZo raZiel about 3 years*delegate the choice of creating a new thread with Task.Run to the caller. And nevermind about the first point, you are totally right not negating the condition in WaitWhile. (sorry I cannot edit my comment after 5 minutes...) -
minus one over 2 yearsIs there a NuGet package that already includes this functionality? -
Sinaesthetic over 2 yearsThere is no thread control here, these are tasks/promises. Async so it doesn't block whatever thread it ends up on, particularly when working with file locks. -
TheGrovesy about 2 yearsCan I just, if the 'condition' is never true yet the 'timeout' is reached wont that task continue to run indefinitely? Should there be a cancellation token for that task which is cancelled with the 'timeout'? If so how might that look? -
Lucas 12 monthsthis solution is best.