如何在没有性能损失的情况下同步等待ValueTask?

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

How can I synchronously wait for ValueTask without performance loss?

问题

首先,我们的平台是C# 9.0和.NET Core 7.0。

假设我必须在同步方法中等待ValueTask完成。在处理这种情况下的任务时,我通常使用以下方案:

var task = Get_Task_PlaceHolderMethod();
task.Wait(timeoutedToken);
var value = task.Result // 从这里开始使用

否则,我只需使用await

那么,如何在等待ValueTask时使用类似的方法?它们没有任何.Wait方法,我只能将其转换为任务并使用.AsTask(),但这样我会失去ValueTask创建任务实例的好处。

我尝试编写了一些逻辑,但总是得到比以前性能更差的代码。如下所示,我不得不创建一个任务的实例,因此上述影响也适用。我无法从ValueTask中获益。

private static void SyncallyAwait(Func<ValueTask> getTask, TimeSpan timeout)
{
    var operationWaiter = new CancellationTokenSource();
    var v_task = getTask();
    var timeouter = Task.Delay(timeout, operationWaiter.Token);
    while ( // 等待完成
        v_task.IsCompleted is false &&
        timeouter.IsCompleted is false
    ){ }

    if (timeouter.IsCompleted)
        throw ERROR_TaskTimeouted();

    operationWaiter.Cancel();

    if (v_task.IsCompleted is false)
        throw ERROR_TaskNotAccessible();

    // 抛出任何结果异常
    v_task.GetAwaiter().GetResult();
}

还有另一种可能性,我可以尝试使用v_task的开始时间,并接收一个TimeSpan来在循环中计算当前时间,同时检查Now - start_time是否大于限制的timeSpan。但这似乎不太合适。

英文:

First of all, our platform for this question is C# 9.0 and .NET core 7.0.
Lets suppose that I have to wait for a ValueTask to complete in a synchronous method. When dealing with tasks in this context, I generally use the following scheme:

var task = Get_Task_PlaceHolderMethod();
task.Wait(timeoutedToken);
var value = task.Result // use from here

Otherwise I just await, normally.

how can I use something like this to wait for ValueTasks? They don't have any .Wait method and all I can do is convert it to task with .AsTask(), but then I will lose the benefits of the ValueTask creating an instance of a task to wrap it.

I tried to code some logic but always end up with less performant code than before.
As following I had to create an instance of a task, so the above implications applies too.
I have no benefit from the ValueTask.

private static void SyncallyAwait(Func&lt;ValueTask&gt; getTask, TimeSpan timeout)
{
    var operationWaiter = new CancellationTokenSource();
    var v_task = getTask();
    var timeouter = Task.Delay(timeout, operationWaiter.Token);
    while ( // wait until done
        v_task.IsCompleted is false &amp;&amp;
        timeouter.IsCompleted is false
    ){ }

    if (timeouter.IsCompleted)
        throw ERROR_TaskTimeouted();

    operationWaiter.Cancel();

    if (v_task.IsCompleted is false)
        throw ERROR_TaskNotAccessible();

    //throw any resulting exception
    v_task.GetAwaiter().GetResult();
}

private static readonly Func&lt;ApplicationException&gt; ERROR_TaskNotAccessible = () =&gt; new($&quot;&quot;&quot;
    Its not possible to ensure that the valuetask is done running, which means that the 
    result is not accurate or safe to access without putting the thread on deadlock.
    to prevent the unexpected lack of response this exception has been thrown
&quot;&quot;&quot;);

private static readonly Func&lt;TimeoutException&gt; ERROR_TaskTimeouted = () =&gt; new($&quot;&quot;&quot;
    MovingOperation exceeded the sync waiter timeout. The database didn&#39;t returned in
    reasonable time expected.
&quot;&quot;&quot;);

Another possibility I could try is to use the start time of the v_task and receive a TimeSpan to count the current time in the loop while, checking if the Now - start_time is
greater than the limit timeSpan. But that doesn't seem right.

答案1

得分: 2

我并没有经常使用值任务。但我理解主要的好处是避免在结果已经可用且异步方法可以同步完成时分配Task对象。所以我认为正确的方法应该是:

var valueTask = getTask();
if (valueTask.IsCompleted)
{
    return valueTask.Result;
}
return valueTask.AsTask().Result;

如果getTask()确实是同步运行的,那么返回的值任务将已经完成,您只需检查并返回结果。如果它没有同步完成,您必须将其转换为任务并等待任务完成。我不认为这会引起任何性能影响。

您的示例似乎在自旋等待值任务,这几乎肯定不是一个好的方法。

英文:

I have not used value tasks that much. But my understanding is that the primary benefit is avoiding allocation of a Task object when the result is already available and the async method can complete synchronously. So I think the correct approach should be:

var valueTask = getTask();
if(valueTask.IsCompleted){
    return valueTask.Result;
}
return valueTask.AsTask().Result;

If getTask() was really run synchronously, then the returned value task will already be completed, and you can just check this and return the result. If it did not complete synchronously you have to convert it to a task and wait on the task. I do not see any reason why this should cause any performance impact.

Your example seem to be spinwaiting on the value task, and that is almost certainly not a good approach.

huangapple
  • 本文由 发表于 2023年7月27日 20:41:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76779838.html
匿名

发表评论

匿名网友

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

确定