如何在继续执行后续代码之前正确等待条件满足?

huangapple go评论72阅读模式
英文:

How to properly wait for conditions to be met before continuing to execute subsequent code?

问题

在执行后续代码之前,我们需要确保满足一些条件,有没有更好的方法来替代while循环?

bool conditionA; // 在另一个任务中设置
bool conditionB; // 在另一个任务中设置

private async Task HandleData()
{
    await Task.Run(() =>
    {
        while (!(this.conditionA && this.conditionB))
        {
            // 这里什么都不做
        }
    });

    // 下一步
    // ...
    // 下一步
}

注意:在分配conditionA和conditionB之前可能会触发HandleData方法。可以使用自定义事件来实现吗?

英文:

We need to make sure some conditions are met before executing the subsequent code, is there a better way to replace the while loop?

bool conditionA; // set in another Task
bool conditionB; // set in another Task

private async Task HandleData()
{
    await Task.Run(() =>
    {
        while (!(this.conditionA && this.conditionB))
        {
            // do nothing here
        }
    });

    // next steps
    // ...
    // next steps
}

Note: Method HandleData may be triggered before conditionA and conditionB are assigned.

Can it be implemented using custom events?

答案1

得分: 3

可以使用 TaskCompletionSource 来实现这个目的。

var tcsa = new TaskCompletionSource();
var tcsb = new TaskCompletionSource();

Task.Run(async () => {
  await Task.Delay(10000);
  tcsa.SetResult();
  // 或者 tcsa.TrySetResult();
});

Task.Run(async () => {
  await Task.Delay(10000);
  tcsb.SetResult();
  // 或者 tcsb.TrySetResult();
});

// 等待两个源完成
await Task.WhenAll(tcsa.Task, tcsb.Task);

也可以使用泛型的 TaskCompletionSource,以便在信号后返回一些值给核心。

英文:

You can use TaskCompletionSource for this purpose

var tcsa = new TaskCompletionSource();
var tcsb = new TaskCompletionSource();

Task.Run(async () => {
  await Task.Delay(10000);
  tcsa.SetResult();
  // or tcsa.TrySetResult();
});

Task.Run(async () => {
  await Task.Delay(10000);
  tcsb.SetResult();
  // or tcsb.TrySetResult();
});

// Wait for both sources to complete
await Task.WhenAll(tcsa.Task, tcsb.Task);

it is also possible to use Generic TaskCompletionSource in case you want to return some value to the core after signal.

答案2

得分: 0

以下是您要翻译的内容:

您可以通过将bool值替换为ManualResetEventSlim,然后使用WaitHandle.WaitAll来等待这两个条件都变为已发信号状态。

可编译的控制台应用程序示例:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Console1
{
    public static class Program
    {
        public static void Main()
        {
            using var conditionA = new ManualResetEventSlim(initialState: false);
            using var conditionB = new ManualResetEventSlim(initialState: false);

            Task.Run(() => signalAfterDelay(conditionA, TimeSpan.FromSeconds(4)));
            Task.Run(() => signalAfterDelay(conditionB, TimeSpan.FromSeconds(8)));
        
            Console.WriteLine("主线程正在等待完成。");

            waitForCompletion(conditionA.WaitHandle, conditionB.WaitHandle);

            Console.WriteLine("主线程完成等待。");
            Console.ReadLine();
        }

        static void signalAfterDelay(ManualResetEventSlim condition, TimeSpan delay)
        {
            Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 正在休眠 {delay}");
            Thread.Sleep(delay);
            Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 正在发信号条件");
            condition.Set();
        }

        static void waitForCompletion(WaitHandle conditionA, WaitHandle conditionB)
        {
            Console.WriteLine("等待两个条件都变为真");

            WaitHandle.WaitAll(new[] { conditionA, conditionB });

            Console.WriteLine("等待两个条件都变为真完成");
        }
    }
}

请注意,此代码假定条件仅发信号一次。如果它们需要被重置,那么如何执行重置操作不在您当前提供的问题范围内,因为它没有提供关于在何种状态下重置条件的信息。

英文:

You could do this by replacing the bool values with ManualResetEventSlim and then using WaitHandle.WaitAll to wait for both the conditions to become signalled.

Sample compilable console application:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Console1
{
    public static class Program
    {
        public static void Main()
        {
            using var conditionA = new ManualResetEventSlim(initialState: false);
            using var conditionB = new ManualResetEventSlim(initialState: false);

            Task.Run(() => signalAfterDelay(conditionA, TimeSpan.FromSeconds(4)));
            Task.Run(() => signalAfterDelay(conditionB, TimeSpan.FromSeconds(8)));
        
            Console.WriteLine("Main thread waiting for completion.");

            waitForCompletion(conditionA.WaitHandle, conditionB.WaitHandle);

            Console.WriteLine("Main thread finished waiting for completion.");
            Console.ReadLine();
        }

        static void signalAfterDelay(ManualResetEventSlim condition, TimeSpan delay)
        {
            Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} is sleeping for {delay}");
            Thread.Sleep(delay);
            Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} is signalling the condition");
            condition.Set();
        }

        static void waitForCompletion(WaitHandle conditionA, WaitHandle conditionB)
        {
            Console.WriteLine("Waiting for both conditions to become true");

            WaitHandle.WaitAll(new[] { conditionA, conditionB });

            Console.WriteLine("Finished waiting for both conditions to become true");
        }
    }
}

Note that this code assumes that the conditions are only signalled once. If they must be reset, then how to do that is outside the scope of your question as it currently stands, since it contains no information about the state under which the conditions are reset.

答案3

得分: -1

Here is the translated code portion:

我通常为这些问题编写一些轻量级扩展:

public static class DelayHelper
{
    public const double GoldenRatio = 1.61803398874989484820458683436;
    
    /// <summary>
    /// 等待条件为真。
    /// </summary>
    /// <param name="condition"></param>
    /// <param name="ct"></param>
    /// <param name="delayProvider"></param>
    /// <returns></returns>
    public static async Task WaitTrueAsync(Func<Task<bool>> condition, CancellationToken ct, Func<int, TimeSpan> delayProvider = null)
    {
        delayProvider ??= x => ExponentialDelayPolicy(x, TimeSpan.FromMilliseconds(1), TimeSpan.FromSeconds(5));
        for (var i = 0; ; i++)
        {
            if (!await condition().ConfigureAwait(false))
            {
                break;
            }
            await Task.Delay(delayProvider(i), ct).ConfigureAwait(false);
        }
    }
    
    public static TimeSpan ExponentialDelayPolicy(int index, TimeSpan minDelay, TimeSpan maxDelay, double @base = GoldenRatio)
    {
        var maxIndex = (int)Math.Ceiling(Math.Log(maxDelay.Ticks / (double)minDelay.Ticks, @base));
        var delay = TimeSpan.FromTicks(index < maxIndex
            ? (long)Math.Ceiling(Math.Min(minDelay.Ticks * Math.Pow(@base, index), maxDelay.Ticks))
            : maxDelay.Ticks);
        return delay;
    }
    
    public static TimeSpan ConstantDelayPolicy(TimeSpan delay)
    {
        return delay;
    }
}

Usage将等待条件满足,然后您可以设置一些内容:

await DelayHelper.WaitTrueAsync(() => !(this.conditionA && this.conditionB), ct);

//在这里执行一些操作

当然,如果您可以更改conditionA/B - 最好将它们替换为TaskCompletionSource,然后等待其完成。我的扩展主要用于一些在慢速传输上引发的事件的拉模型 - 网络、文件等。

PS 指数延迟策略通常更适用于IO,但如果您认为它很慢,可以在那里设置一些恒定延迟。
英文:

I usually write some light extensions for those problems:

public static class DelayHelper
{
    public const double GoldenRatio = 1.61803398874989484820458683436;
    
    /// &lt;summary&gt;
    /// Wait until condition is true.
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;condition&quot;&gt;&lt;/param&gt;
    /// &lt;param name=&quot;ct&quot;&gt;&lt;/param&gt;
    /// &lt;param name=&quot;delayProvider&quot;&gt;By default this function checks condition with exponentially increasing delay. The best base was calculated to be GoldenRation and delay capped at 5 seconds,
    /// other bases give chaotic losses in time but can improve check performance. 5 second cap used solely because checks considered to be fast. For longer operations its better to use longer cap.&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    public static async Task WaitTrueAsync(Func&lt;Task&lt;bool&gt;&gt; condition, CancellationToken ct, Func&lt;int, TimeSpan&gt; delayProvider = null)
    {
        delayProvider ??= x =&gt; ExponentialDelayPolicy(x, TimeSpan.FromMilliseconds(1), TimeSpan.FromSeconds(5));
        for (var i = 0; ; i++)
        {
            if (!await condition().ConfigureAwait(false))
            {
                break;
            }
            await Task.Delay(delayProvider(i), ct).ConfigureAwait(false);
        }
    }
    
    public static TimeSpan ExponentialDelayPolicy(int index, TimeSpan minDelay, TimeSpan maxDelay, double @base = GoldenRatio)
    {
        var maxIndex = (int)Math.Ceiling(Math.Log(maxDelay.Ticks / (double)minDelay.Ticks, @base));
        var delay = TimeSpan.FromTicks(index &lt; maxIndex
            ? (long)Math.Ceiling(Math.Min(minDelay.Ticks * Math.Pow(@base, index), maxDelay.Ticks))
            : maxDelay.Ticks);
        return delay;
    }
    
    public static TimeSpan ConstantDelayPolicy(TimeSpan delay)
    {
        return delay;
    }
}

Usage will wait until condition is met, then you can set something:

await DelayHelper.WaitTrueAsync(()=&gt; !(this.conditionA &amp;&amp; this.conditionB), ct);

//do something here

Of course if you can change conditionA/B - it is better replace them with TaskCompletionSource and just wait for it to complete. My extension is primarly pull model for some events rised over slow transports - network, files, etc.

PS Exponential delay policy is usually better for IO, but you can just set some constant delay there if you think it is slow.

huangapple
  • 本文由 发表于 2023年5月25日 13:50:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76329271.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定