等待事件 - 比轮询更好的解决方案?

Wait for an Event - Better solution than polling?

我正在解决现有项目的问题。我们想要读取一个 ADC 值,通常我们会使用即发即弃的概念。我们询问该值,并在读取该值后引发一个事件。但是现在我必须实现一个函数,它 returns 直接是一个值。我的想法是通过轮询解决这个问题。

public class Information
{
    public delegate void NewADCValueArrived(double newValue);

    private event NewADCValueArrived newAdcValue;

    private double getADCValueDirectly()
    {
        double value = -1;

        NewADCValueArrived handler = delegate(double newValue)
        {
                value = newValue;
        };
        newAdcValue += handler;

        askFornewValues(); //Fire and forget

        timeout = 0;
        while(value != -1 && timeout <100)
        {
            timeout++;
            Thread.sleep(1); //Want to avoid this!! because sleeping for 1 ms is very inaccurate
        }

        newAdcValue -= handler;

        if (value != -1)
        {
            return value;
        }
        else
        {
            throw Exception("Timeout");
        }
    }
}

现在的问题是,我想避免轮询。因为通常响应甚至快于 1 毫秒,我想尽快完成该功能。 你有更好的办法解决这个问题吗?

在 c#-Documentation 中,我找到了一些关于 WaitHandlers 的信息,但我无法将它们集成到我的程序中。 (https://msdn.microsoft.com/en-us/library/system.threading.waithandle)

您可以使用 TaskCompletionSource 将事件抽象为 Task。您可以参考 this question 了解如何操作。您甚至不需要参考答案;问题本身说明了如何。

获得 Task 后,您无需再进行投票。您可以做各种有趣的事情,例如 WaitContinueWith 甚至 await.

对于超时,您可以使用调用 TaskCompletionSource.SetCanceledTimer

关于如何取消订阅:(在评论中询问)

public class MyClass
{
    public event Action OnCompletion;
}

public static Task FromEvent(MyClass obj)
{
    TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
    Action completion = null;
    completion = () =>
    {
        tcs.SetResult(null);

        obj.OnCompletion -= completion;
    };

    obj.OnCompletion += completion;

    return tcs.Task;
}

如果您可以选择,我可能会选择基于任务的解决方案。

否则,您可以设置一个 AutoResetEvent 并等待它被事件处理程序触发。

private double getADCValueDirectly()
{
    double value = -1;
    AutoResetEvent eventCompleted = new AutoResetEvent(false);
    NewADCValueArrived handler = delegate(double newValue)
    {
        value = newValue;
        // signal the waithandle
        eventCompleted.Set();
    };
    newAdcValue += handler;

    askFornewValues(); //Fire and forget
    eventCompleted.WaitOne(); // optionally enter a timeout here

    newAdcValue -= handler;

    if (value != -1)
    {
        return value;
    }
    else
    {
        throw Exception("Timeout");
    }
}

http://www.albahari.com/threading/part2.aspx#_AutoResetEvent

上有一个关于 C# 线程处理的优秀教程

如果它真的是实时并且你不能让调度程序采取行动,你可以做一个忙等待

    timeout = 0;
    while(value != -1 && timeout <100000)
    {
        timeout++;
        for(int j= 0; j < 100; j++); // keep CPU busy
    }

这假设您的 value 被另一个线程修改并且您允许程序短时间冻结。从好的方面来说,没有 windows 调度(任务、事件等)会影响你。