SpinWait.SpinUntil的替代品

For*_*ore 3 c# performance task-parallel-library

我们在我们的应用程序中使用Task.在一个类中,我们希望触发在并行任务上运行的更新.电话看起来像:

            Maintenance.RecievedMessage += new NotificationHandler(Maintenance_RecievedMessage);
            Maintenance.checkLastXML = false;
            Maintenance.NeedToUpdateFromCarrier(userId);

        SpinWait.SpinUntil(() => isCompleted == true);
        return true;
Run Code Online (Sandbox Code Playgroud)

所以我们挂钩了一个在Maintenance.NeedToUpdateFromCarrier(userId); 方法运行完毕.完整的方法如下:

 private void Maintenance_RecievedMessage(IsCompleted changeargs)
    {
        isCompleted = true;
    }
Run Code Online (Sandbox Code Playgroud)

所以我们正在等待NeedToUpdateFromCarrier方法,一旦它完成就触发它完成的事件,我们捕获事件并将属性isComplete设置为true,这就是当SpinWait.SpinUntil完成时,我们继续.

由于SpinWait.SpinUntil对CPU非常重,我现在正在寻找这个问题的替代解决方案.

Han*_*ant 14

重要的是要了解何时旋转等待是合适的.它的情况非常少.旋转等待优化线程上下文切换.无论何时等待某事,WaitHandle.Wait()之类的调用都会阻塞线程并产生处理器.当操作系统找到一些其他线程来执行有用的工作时,它会执行线程上下文切换.

线程上下文切换非常昂贵.没有确切的数字,因为它取决于yield-to thread运行的位置,当该线程在另一个进程或保护环(驱动程序)中运行时会产生额外的开销.它的成本在2000到10,000个周期之间.

那些是cpu周期,并没有取得多大成就.只是开销没有完成真正的工作.如果您知道等待条件总是需要少于20,000个周期,则可以优化线程代码.只是延迟你的线程(旋转)将确保不需要昂贵的上下文切换.这不是像Thread.Sleep()这样的正常延迟,它会产生,它是一个小循环,可以燃烧100%核心.抛出一些聪明的东西,就像在只有一个核心的机器上旋转一样,永远不会有效,所以它无论如何都会产生效果.

显然,如果等待条件持续超过20,000个周期,这将无法正常工作.现在你处于明智选择的另一端,你确实希望在那些情况下屈服.不仅仅是为了避免在没有完成任何事情时烧掉cpu,尤其是因为屈服使得现在更有可能更快地满足等待条件.因为增加设置等待条件的线程可以获得足够的cpu周期来完成其工作的几率.

有很多证据表明你的代码就是这种情况.您明确要求代码在旋转之前执行某些操作.它需要一个事件处理程序来指示完成.Mucho代码需要运行.最令人信服的是,你看到很多cpu被烧毁了.TaskMgr.exe中1%的负载大约是2000万个cpu周期.

请使用等待事件,例如AutoResetEvent.注意所需的结构变化,isCompleted不再是bool.你在完成处理程序中调用Set(),Wait()来阻止它.


dtb*_*dtb 6

您可以使用ManualResetEventSlim:

var signal = new ManualResetEventSlim();

Maintenance.RecievedMessage += delegate { signal.Set(); };
Maintenance.checkLastXML = false;
Maintenance.NeedToUpdateFromCarrier(userId);

signal.Wait();
return true;
Run Code Online (Sandbox Code Playgroud)