Подтвердить что ты не робот

С# Подождите, пока не будет выполнено условие

Я пытаюсь написать код, который выполняется при выполнении условия. В настоящее время я использую while... loop, который, как я знаю, не очень эффективен. Я также смотрю на AutoResetEvent(), но я не знаю, как его реализовать, чтобы он продолжал проверять, пока условие не будет истинным.

Кроме того, код живет внутри метода async, так что может быть какой-то ожидание может работать?

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() без застревания процессора в цикле.

Примечание. В Excel нет обработчика событий, который был бы поднят, когда он не находится в режиме редактирования.

4b9b3361

Ответ 1

По крайней мере, вы можете изменить свой цикл от оживленного до медленного опроса. Например:

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

Ответ 2

Вы можете использовать обработчик ожидания потока

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

Ответ 3

Вы можете использовать SpinUntil, который встроен в .net-framework. Обратите внимание: этот метод вызывает высокую загрузку процессора.

Ответ 4

Закончилось писать это сегодня и, кажется, все в порядке. Ваше использование может быть:

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

Пример использования: https://dotnetfiddle.net/Vy8GbV

Ответ 5

Перекопав много вещей, наконец, я нашел хорошее решение, которое не вешает 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;
    }
}

Ответ 6

Для этого вы можете использовать результат async и делегат. Если вы читаете документацию, то должно быть ясно, что делать. Я могу написать код примера, если хотите, и прикрепить его к этому ответу.

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 на этом компьютере, но, надеюсь, он доставит вас туда, куда вам нужно добраться.