C# Wait until condition is true

152,160

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.

Share:
152,160
Keylee
Author by

Keylee

Updated on January 14, 2021

Comments

  • Keylee
    Keylee over 2 years

    I 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
    Peyman about 8 years
    Is it your answer? Whether isExcelInteractive access to waitHandle, if not you need to share this object between both methos
  • Keylee
    Keylee about 8 years
    I am not sure this answer would work. What is calling isExcelInteractive()? This doesnt seem check isExcelInteractive() continuously
  • Keylee
    Keylee about 8 years
    I did thought about this. Is this a common practice? I am still very new to programming.
  • Keylee
    Keylee about 8 years
    Please do, that will help me understand quicker :)
  • Peyman
    Peyman about 8 years
    so you have to use @Ben solution, or call isExcelInteractive() method with specific interval
  • Keylee
    Keylee about 8 years
    I like his implementation too. Very simple.
  • Peyman
    Peyman about 8 years
    Yes it is. If you need only in one place you can use his solution, otherwise you can use my (complicated) solution :)
  • Keylee
    Keylee about 8 years
    Thank you for your input. I will keep this in my pocket of tools :)
  • Umair Jameel
    Umair Jameel almost 7 years
    Error: Can't use GreatBigMethod before it is declared. How to resolve it?
  • ArieDov
    ArieDov over 4 years
    Seems like it never throws the exception.
  • Oğuzhan Soykan
    Oğuzhan Soykan over 4 years
    @ArieDov, it depends what you want tho. If it is foreseeable that predicate throws an exception then yes you need to catch and set as a tcs result
  • NSFW
    NSFW almost 4 years
    While 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
    Ustin almost 4 years
    could you please share how should "Func<bool> condition" looks ? Thanks in advance, cause you have one of the most beautiful approaches.
  • Sinaesthetic
    Sinaesthetic almost 4 years
    @Ustin I added a link to a fiddle with example usage of WaitUntil(...) as an inline Func<bool> and another example as a function pointer of the same signature. It would be the same for WaitWhile(...). 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
    ygoe over 3 years
    This will still keep the CPU busy, just in a different thread while the original thread is free. What is won here?
  • fibriZo raZiel
    fibriZo raZiel about 3 years
    A couple of caveats: 1. Shouldn't it be while( !condition() ) (negated) ? 2. Why do you use async for your threaded polling instead of Task.Run( () => { while (!condition()) { Thread.Sleep(pollDelay); } }); 3. I would add a cancellation token while (!condition() && !ct.IsCancellationRequested) and Task.Delay(timeout, ct); 4. I would also rename the method to WaitWhileAsync and WaitUntilAsync. 5. Finally, I think you should delgate the choice of using a new thread with Task.Run and maybe use instead a non-threaded polling async method such as await PollConditionAsync().
  • fibriZo raZiel
    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
    minus one over 2 years
    Is there a NuGet package that already includes this functionality?
  • Sinaesthetic
    Sinaesthetic over 2 years
    There 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
    TheGrovesy about 2 years
    Can 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
    Lucas 12 months
    this solution is best.