C#等到条件成立

时间:2015-03-17 00:36:27

标签: c# loops

我正在尝试编写在满足条件时执行的代码。目前,我正在使用while ...循环,我知道效率不高。我也在看AutoResetEvent(),但我不知道如何实现它,以便它一直检查,直到条件为真。

代码也恰好存在于异步方法中,所以可能某种等待可以工作吗?

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
        }
    }

我需要找到一种方法来继续检查isExcelInteractive()而不会在循环中卡住CPU。

注意:Excel中没有事件处理程序在未处于编辑模式时引发。

8 个答案:

答案 0 :(得分:25)

至少你可以将你的循环从忙等待改为慢转。例如:

    while (!isExcelInteractive())
    {
        Console.WriteLine("Excel is busy");
        await Task.Delay(25);
    }

答案 1 :(得分:8)

您可以使用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");
 }

然后其他一些工作需要设置你的处理程序

void isExcelInteractive()
{
   /// Do your check
   waitHandle.Set()
}

更新: 如果要使用此解决方案,则必须以特定间隔连续调用isExcelInteractive():

var actions = new []{isExcelInteractive, () => Thread.Sleep(25)};
foreach (var action in actions)
{                                      
    action();
}

答案 2 :(得分:5)

此实现完全基于Sinaesthetic的实现,但是添加了CancellationToken并保持相同的执行线程和上下文;也就是说,根据是否需要在同一线程中评估Task.Run(),将condition的使用权委派给调用者。

此外,请注意,如果您真的不需要抛出TimeoutException并且打破循环就足够了,则可能要使用cts.CancelAfter()new CancellationTokenSource(millisecondsDelay)而不是将timeoutTaskTask.DelayTask.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();
                }
            }
        }
    }
}

答案 3 :(得分:3)

今天终于写完了,似乎还可以。您的用法可能是:

await TaskEx.WaitUntil(isExcelInteractive);

代码(包括逆运算)

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();
    }
}

答案 4 :(得分:2)

您可以使用.net框架中内置的SpinUntil

答案 5 :(得分:2)

试试这个

void Function()
{
    while (condition) 
    {
        await Task.Delay(1);
    }
}

这将使程序等待,直到条件不为真。 您可以通过添加“!”来反转它前面的条件,以便它会一直等到条件为真。

答案 6 :(得分:0)

在挖掘了很多东西之后,最后,我想出了一个不错的解决方案,它不会挂起CI :)使它适合您的需求!

public static Task WaitUntil<T>(T elem, Func<T, bool> predicate, int seconds = 10)
{
    var tcs = new TaskCompletionSource<int>();
    using(var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(seconds)))
    {
        cancellationTokenSource.Token.Register(() =>
        {
            tcs.SetException(
                new TimeoutException($"Waiting predicate {predicate} for {elem.GetType()} timed out!"));
            tcs.TrySetCanceled();
        });

        while(!cancellationTokenSource.IsCancellationRequested)
        {
            try
            {
                if (!predicate(elem))
                {
                    continue;
                }
            }
            catch(Exception e)
            {
                tcs.TrySetException(e);
            }

            tcs.SetResult(0);
            break;
        }

        return tcs.Task;
    }
}

答案 7 :(得分:-2)

您可以为此使用异步结果和委托。如果您阅读the documentation,那么它应该清楚地说明该做什么。如果你愿意,我可以写一些示例代码并将其附加到这个答案。

Action isExcelInteractive = IsExcelInteractive;

private async void btnOk_Click(object sender, EventArgs e)
{
    IAsyncResult result = isExcelInteractive.BeginInvoke(ItIsDone, null);
    result.AsyncWaitHandle.WaitOne();
    Console.WriteLine("YAY");
} 

static void IsExcelInteractive(){
   while (something_is_false) // do your check here
   {
       if(something_is_true)
          return true;
   }
   Thread.Sleep(1);
}

void ItIsDone(IAsyncResult result)
{
   this.isExcelInteractive.EndInvoke(result);
}

如果这段代码没有100%完成,我很抱歉,我不会在这台电脑上安装Visual Studio,但希望它可以让你到达你需要的地方。