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

How can I synchronously wait for ValueTask without performance loss?

提问人:James Jonatah 提问时间:7/27/2023 最后编辑:Jason AllerJames Jonatah 更新时间:7/27/2023 访问量:291

问:

首先,我们针对此问题的平台是 C# 9.0 和 .NET core 7.0。 假设我必须等待同步方法中的 a 完成。在这种情况下处理任务时,我通常使用以下方案:ValueTask

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

否则我只是,正常。await

我怎样才能使用这样的东西来等待 ValueTasks?他们没有任何方法,我所能做的就是将其转换为任务,但这样我就会失去创建任务实例来包装它的好处。.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 ( // wait until done
        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();

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

private static readonly Func<ApplicationException> ERROR_TaskNotAccessible = () => new($"""
    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
""");

private static readonly Func<TimeoutException> ERROR_TaskTimeouted = () => new($"""
    MovingOperation exceeded the sync waiter timeout. The database didn't returned in
    reasonable time expected.
""");

我可以尝试的另一种可能性是使用 和 receive a 的开始时间来计算循环中的当前时间,同时检查是否是 大于限制。但这似乎不对。v_taskTimeSpanNow - start_timetimeSpan

C# asp.net 异步 ValueTask

评论

1赞 Theodor Zoulias 7/27/2023
顺便说一句,意思是“异步等待”。因此,该方法的名称不合适。 可能是一个更好的名字,或者只是.awaitSyncallyAwaitSyncallyWaitWait
1赞 Theodor Zoulias 7/27/2023
相关:除非实例已经完成,否则不应直接访问 ValueTask 实例的结果

答:

3赞 JonasH 7/27/2023 #1

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

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

如果真的是同步运行的,那么返回值任务就已经完成了,你可以检查一下并返回结果。如果它没有同步完成,则必须将其转换为任务并等待任务。我看不出有任何理由会对性能造成任何影响。getTask()

你的例子似乎在价值任务上旋转等待,这几乎可以肯定不是一个好方法。

评论

0赞 James Jonatah 7/27/2023
调用 valueTask 时,该方法返回 IsCompleted = true 的控件,表示任务完成,无需等待任何操作,对吧?然后。结果是可以安全访问,没有死锁的可能性,我相信是这样,很高兴它可以这么简单
2赞 Theodor Zoulias 7/27/2023
返回值Task.AsTask()。结果;-- 我认为最好这样做,以便直接暴露异常,而不是包装在.return valueTask.AsTask().GetAwaiter().GetResult();AggregateException
0赞 James Jonatah 7/27/2023
在这种情况下,我认为在调用逻辑之前检查 IF 以防止不必要的分配是相关的。valueTask.IsfaultedAsTask()
1赞 JonasH 7/27/2023
@JamesJonatah From : “他的属性只能访问一次,并且只能在此 ValueTask<TResult> 完成后访问”。IsComplete 应该可以安全访问,并且不应将值从 true 更改为 false。ValueTask.Result
0赞 JonasH 7/27/2023
@JamesJonatah 如果任务出错,您可能不必担心内存分配的微小开销。如果您的代码由于某些原因以高频率抛出异常,请修复该问题!创建异常的成本很高!在一些极端情况下,显式检查故障是有用的,但“过早的优化是万恶之源”这句话可能适用于这里。