【问题标题】:Correct way to wait for Event等待事件的正确方法
【发布时间】:2015-11-30 07:13:57
【问题描述】:

我们在函数中等待事件发生。但我不认为代码是正确的(它有效,但对我来说它看起来不对!)。

起初,这是我同事写的代码:

    public string Dispatch(Request data)
    {
        var uri = ...
        string _result = null;
        using (var ws = new WebSocket(uri))
        {
            ws.OnMessage += (sender, e) =>
            {
                _result = e.Data;
            };

            ws.Send(request);
            while (_result == null)
            {
                Thread.Sleep(10);
            }

            return _result;
        }
    }

有没有更好的方法来实现这一点?我想我可以使用 AutoResetEvent,但这更好吗?有没有办法实现线程在等待答案时可以重用的代码? (我知道如何用 TaskCompletitionSource 来做,但这对 Sync Functions 也正确吗?)

我的想法是:

    public string Dispatch(Request data)
    {
        var uri = ...

        using (var ws = new WebSocket(uri))
        {
            TaskCompletionSource<Guid> tcs;
            ws.OnMessage += (sender, e) =>
            {
                tcs.SetResult(e.Data);
            };

            ws.Send(request);

            return tcs.Task.Result;
        }
    }

    public string Dispatch(Request data)
    {
        var uri = ...
        string _result = null;
        var event = new AutoResetEvent(false);
        using (var ws = new WebSocket(uri))
        {
            TaskCompletionSource<Guid> tcs;
            ws.OnMessage += (sender, e) =>
            {
                _result = e.Data;
                event.Set();
            };

            ws.Send(request);

            event.WaitOne();
            return _result;
        }
    }

【问题讨论】:

标签: c# multithreading events task


【解决方案1】:

有没有更好的方法来实现这一点?

几乎任何形式的等待都会比原始代码更好,原始代码每 10 毫秒唤醒一次,只是为了检查一个标志。唯一会更糟的是根本不打电话给Thread.Sleep()。但这只会更糟。

我想我可以使用 AutoResetEvent,但这更好吗?

恕我直言,最好使用 TaskCompletionSourceawait。这样做可以解决您的下一个问题:

有没有办法实现线程在等待答案时可以重复使用的代码?

即使用await,线程实际上被释放用于其他用途。调用await 的方法将在await 语句处返回,允许该线程继续执行其他代码。当然,这意味着调用方法需要以某种方式处理异步;通常它的工作方式是async 被一直推回到线程的顶部,例如在某种事件调度循环中,例如在 Winforms 或 WPF 中找到的。

您没有具体说明为什么贵公司不使用await。如果限制是由于被锁定在 .NET 的早期版本中,您可能也无法使用 TaskCompletionSource,因为它仅在 .NET 4 及更高版本中可用。

即使您使用 .NET 4(或更高版本)并且可以使用 TaskCompletionSource,等待任务也无法解决您对释放线程以供其他用途的担忧。这可能是可寻址的,也可能是不可寻址的; Task 类确实有 ContinueWith() 方法,它允许您显式提供一个继续委托,以便在完成 Task 时调用。但是,如果您的 Dispatch() 方法本身不能在异步上下文中使用——即它在操作完成之前不能返回——那么你别无选择,只能阻止该线程的执行,直到操作完成。

(取决于上下文——这里提供的内容太少,无法提供具体的建议——您可以在等待时从Dispatch() 方法调度其他操作。但这会使设计显着复杂化。恕我直言,最好只升级到 .NET 4.5/C# 5 并使用await...这可能不是一个长期维护问题,而不是尝试破解固有的功能由更现代的框架和语言版本提供)。

最后,我要指出,作为一般规则,您应该避免使用ManualResetEventAutoResetEvent,因为它们基于非托管 Windows 同步对象并且相当“重量级”。 .NET 提供了更高效的本机同步机制。至少,您可以使用Monitor 类,以及Wait()Pulse() 方法;可以使用CountdownEventSemaphoreSlim 找到等效机制。每个都有稍微不同的语义,但都可以用来在线程之间轻松实现一个简单的信号。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-24
    • 2014-11-15
    • 1970-01-01
    • 1970-01-01
    • 2020-08-01
    • 1970-01-01
    • 2017-09-09
    相关资源
    最近更新 更多