【问题标题】:await Task.Run taking longer than expectedawait Task.Run 花费的时间比预期的要长
【发布时间】:2020-04-01 19:15:13
【问题描述】:

下面的方法假设运行(持续时间为毫秒)为案例 0:,但我看到的是该方法可能需要长达 2 秒才能运行 400 毫秒的持续时间。 Task.run 是否可能需要很长时间才能启动?如果有,有没有更好的办法?

private static async void PulseWait(int duration, int axis){
await Task.Run(() =>
{
    try
    {
        var logaction = true;
        switch (axis)
        {
            case 0:
                var sw1 = Stopwatch.StartNew();
                if (duration > 0) duration += 20; // allowance for the call to the mount
                while (sw1.Elapsed.TotalMilliseconds <= duration) { } // wait out the duration
                _isPulseGuidingRa = false;
                logaction = false;
                break;
            case 1:
                var axis2Stopped = false;
                var loopcount = 0;

                switch (SkySettings.Mount)
                {
                    case MountType.Simulator:
                        while (!axis2Stopped && loopcount < 30)
                        {
                            loopcount++;
                            var statusy = new CmdAxisStatus(MountQueue.NewId, Axis.Axis2);
                            var axis2Status = (AxisStatus)MountQueue.GetCommandResult(statusy).Result;
                            axis2Stopped = axis2Status.Stopped;
                            if (!axis2Stopped) Thread.Sleep(10);
                        }
                        break;
                    case MountType.SkyWatcher:
                        while (!axis2Stopped && loopcount < 30)
                        {
                            loopcount++;
                            var statusy = new SkyIsAxisFullStop(SkyQueue.NewId, AxisId.Axis2);
                            axis2Stopped = Convert.ToBoolean(SkyQueue.GetCommandResult(statusy).Result);
                            if (!axis2Stopped) Thread.Sleep(10);
                        }
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
                _isPulseGuidingDec = false;
                logaction = false;
                break;
        }

        var monitorItem = new MonitorEntry
        { Datetime = HiResDateTime.UtcNow, Device = MonitorDevice.Telescope, Category = MonitorCategory.Mount, Type = MonitorType.Data, Method = MethodBase.GetCurrentMethod().Name, Thread = Thread.CurrentThread.ManagedThreadId, Message = $"PulseGuide={logaction}" };
        MonitorLog.LogToMonitor(monitorItem);
    }
    catch (Exception)
    {
        _isPulseGuidingDec = false;
        _isPulseGuidingRa = false;
    }
});}

显示所用时间的日志... 33652,2019:07:12:01:15:35.590,13,AxisPulse,Axis1,0.00208903710815278,400,0,True

【问题讨论】:

  • 请注意,您永远不应该拥有async void 方法(事件处理程序除外)。您的 PulseWait 应声明为 private static async Task PulseWait(...) 并在调用时等待。由于整个方法体是一个单独的 Task.Run 调用,因此您也可以将其声明为 private static Task PulseWait(...) { return Task.Run(() =&gt; { ... }); }
  • 您是否尝试过在 case 0: 的 while 循环之后包含由 sw1 测量的经过时间的调试?而且,在 try 块的开头声明,这样你也可以在 switch case 之后调试它,看看它的值?这可能会让您知道在哪里花费的时间比预期的要长。
  • 除了@Clemens 所说的之外,这还取决于您如何称呼它,来自什么样的应用程序等。尝试创建一个minimal reproducible example 来演示核心问题。
  • while (sw.Elapsed &lt;= ...) 循环令人眼花缭乱,它会让你的 CPU 毫无用处。查找Task.Delay()
  • asyncawait 使事情变得简单,并有助于提高整体性能(例如提高 Web 应用程序的整体吞吐量性能),但实际上会损害任何特定任务的性能。如果每一毫秒对您来说都很重要,您将需要自己执行低级任务,例如创建 Thread(如果这确实需要在单独的线程上运行)。

标签: c# wpf async-await task-parallel-library


【解决方案1】:

asyncawait 的目的是让事情变得简单。但就像所有让事情变得简单的事情一样,它也伴随着完全控制正在发生的事情的成本。在这里,一般来说,这确实是异步编程的成本。异步编程的重点是释放当前线程,以便当前线程可以关闭并执行其他操作。但是如果在当前线程上做了其他事情,那么你正在做的事情的继续必须等到它完成。 (即,在任务完成后await 可能不会立即发生)

因此,虽然异步编程将有助于整体性能(例如提高网络应用程序的整体吞吐量性能),但实际上会损害任何一个特定任务的性能。如果每一毫秒对您来说都很重要,那么您也许可以自己完成低级任务,例如创建线程(如果这确实需要在单独的线程上运行)。

这里有一个简单的例子来说明这一点:

var s = new Stopwatch();

// Test the time it takes to run an empty method on a
// different thread with Task.Run and await it.
s.Start();
await Task.Run(() => { });
s.Stop();
Console.WriteLine($"Time of Task.Run: {s.ElapsedMilliseconds}ms");

// Test the time it takes to create a new thread directly
// and wait for it.
s.Restart();
var t = new Thread(() => { });
t.Start();
t.Join();
s.Stop();

Console.WriteLine($"Time of new Thread: {s.ElapsedMilliseconds}ms");

输出会有所不同,但看起来像这样:

Time of Task.Run: 8ms
Time of new Thread: 0ms

在有很多其他事情发生的应用程序中,如果在 await 期间有其他操作使用线程,那么 8ms 可能会更长。

这并不是说您应该使用Threadt.Join() 不是异步操作。它会阻塞线程。所以如果PulseWait运行在UI线程上(如果这是一个UI应用),它会锁定UI线程,这是一个不好的用户体验。在这种情况下,您可能无法避免使用异步代码的成本。

如果这不是一个带有 UI 的应用程序,那么我不明白为什么你需要在不同的线程上完成所有这些工作。也许你可以……不要那样做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-30
    • 1970-01-01
    • 2021-09-18
    相关资源
    最近更新 更多