【问题标题】:Create an awaitable that awaits a method to be executed创建一个等待方法执行的 awaitable
【发布时间】:2014-11-22 22:46:57
【问题描述】:

我需要做的是等待特定方法被执行。

class MyClass
{
    public void TheMethodToAwait()
    {
        // Do something to signal that the method was invoked
    }

    public AnAwaitableObject TheMethodToAwaitWasExecuted()
    {
        // Need help in creating the awaitable object
    }
}

如您所见,我想要一个返回等待对象的方法,以便其他类可以执行以下操作:

await InstanceOfMyClass.TheMethodToAwaitWasExecuted();

我知道创建自定义等待对象的基础知识,但我一直在努力解决这个问题,因为我从未在实践中这样做过。

像往常一样,非常感谢任何帮助,谢谢

附加信息

该方法需要多次等待。实际上它是游戏主循环中的更新。我的异步方法做了一些必须与循环同步的工作。如果游戏以 60 FPS 运行,那么异步方法需要每秒完成 60 次工作。希望现在这更有意义。

我最后是怎么做到的

为了完整起见,我也会发布我的实现:

public class AwaitableEvent<TResult>
{
    private ConcurrentQueue<EventAwaiter<TResult>> _awaiterQueue;

    public AwaitableEvent()
    {
        _awaiterQueue = new ConcurrentQueue<EventAwaiter<TResult>>();
    }

    public EventAwaiter<TResult> GetAwaiter()
    {
        var awaiter = new EventAwaiter<TResult>();

        _awaiterQueue.Enqueue(awaiter);

        return awaiter;
    }

    public void Notify(TResult result)
    {
        var awaiterCount = _awaiterQueue.Count;

        while (awaiterCount-- > 0)
        {
            EventAwaiter<TResult> awaiter;

            if (_awaiterQueue.TryDequeue(out awaiter))
            {
                awaiter.Notify(result);
            }
        }
    }
}

public class EventAwaiter<TResult> : INotifyCompletion
{
    private Action _continuation;
    private TResult _result;

    public bool IsCompleted
    {
        get { return false; }
    }

    public EventAwaiter()
    {
    }

    public void Notify(TResult result)
    {
        _result = result;

        _continuation();
    }

    public void OnCompleted(Action continuation)
    {
        _continuation = continuation;
    }

    public TResult GetResult()
    {
        return _result;
    }
}

【问题讨论】:

  • 考虑签出TaskCompletionSource
  • 为什么不想返回Task?如果您希望调用者 await 该对象,则很少有理由返回任何其他内容。几乎任何时候你看到有人创建一个自定义的等待对象,这纯粹是作为一种学术练习,以更好地了解幕后发生的事情,而不是出于任何实际目的。
  • @Servy 我返回任务没有问题,只是我不知道如何实现我想要的。如果您可以使用 Task 做到这一点,请使用代码示例添加答案。
  • @Dan Briant 您介意用代码示例添加答案吗?
  • @dan 不知道你的方法应该做什么,我怎么可能可能告诉你怎么做?几乎可以肯定,您应该在这里返回Task。如何让Task 返回将高度 取决于此方法的具体用途。创建任务的方法有很多,它们会根据执行的底层操作以及您使用哪些其他工具来创建固有的异步性而有所不同。

标签: c# async-await


【解决方案1】:

现在我更了解您在做什么,我可以推荐一种我为模拟系统采用的方法。我想要一种编写脚本的方法,可以“等待”下一个模拟更新周期,这非常像游戏循环中的帧。我最初使用的是 TaskCompletionSource,但由于在每次等待时都构建它,所以重载是有问题的。我最终创建了一个这样的自定义等待者:

实现“始终不完整”的可等待对象的简单基类(基本上只是要触发的延续集合):

public abstract class Awaiter : INotifyCompletion
{
    private readonly List<Action> _continuations = new List<Action>();

    public bool IsCompleted
    {
        get { return false; }
    }

    public abstract void GetResult();

    public void OnCompleted(Action continuation)
    {
        lock (_continuations)
        {
            var syncContext = SynchronizationContext.Current;
            if (syncContext != null)
            {
                _continuations.Add(() => syncContext.Send(s => continuation(), null));
            }
            else
            {
                _continuations.Add(continuation);
            }                
        }
    }

    public void RunContinuations()
    {
        Action[] continuations;
        lock (_continuations)
        {
            continuations = _continuations.ToArray();
            _continuations.Clear();
        }
        foreach (var continuation in continuations)
        {
            continuation();
        }
    }

    public Awaiter GetAwaiter()
    {
        return this;
    }
}

Simulation 类里面是这个(你可以await CycleExecutedEvent() 使用它):

private readonly CycleExecutedAwaiter _awaiter = new CycleExecutedAwaiter();

        public Awaiter CycleExecutedEvent()
        {
            if (!IsRunning) throw new TaskCanceledException("Simulation has been stopped");
            return _awaiter;
        }

        private sealed class CycleExecutedAwaiter : Awaiter
        {
            public override void GetResult()
            {
                var syncContext = SynchronizationContext.Current as ScriptingSynchronizationContext;
                if (syncContext != null && syncContext.StopRequested)
                    throw new TaskCanceledException("Stop Requested");
            }
        }

希望这与您的用例足够相似以提供帮助。


重要提示:

此实现包含一些您可能想要去除的同步上下文技巧。我需要控制用于脚本目的的线程,并且还想阻止我的更新循环,同时给脚本时间执行。您的要求可能会有所不同;例如,您可能希望使用 Task.Run 异步运行延续。不过,这至少应该为如何开始提供一个框架。

【讨论】:

  • 我仍在消化代码,但它似乎是我想要的。实际上我正在实现类似于脚本系统的东西......我正在尝试理解 SynchronizationContext 的东西,但我认为我可以在 MSDN 上自己研究它。
  • @dan,同步上下文与允许强制中断脚本有关;这有点骇人听闻,可能不太适用于您正在做的事情。如果你只想在你的游戏循环线程的上下文中执行,你可以完全省略它。
  • 是的,我明白了目的。好吧,我的系统会更加健壮,可以通过强制停止脚本执行的方式,所以我会尝试更好地理解你的代码。
  • 我使用了一个简化的实现,但想法是一样的。谢谢。
【解决方案2】:

如果它只是一个信号(不是可以“设置”然后再“取消设置”和“设置”的东西),那么你可以使用TaskCompletionSource&lt;T&gt;

class MyClass
{
  private readonly TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>();
  public void TheMethodToAwait()
  {
    _tcs.TrySetResult(null);
  }

  public Task TheMethodToAwaitWasExecutedAsync()
  {
    return _tcs.Task;
  }
}

【讨论】:

  • 是的,这似乎是朝着正确方向迈出的一步。无论如何,我需要重用它。实际上 MethodToAwait() 是游戏主循环中的更新。我的异步方法需要等待新帧的开始来进行处理,这样如果游戏以 60 FPS 运行,它们每秒可以完成 60 次工作。所以我需要以某种方式重用完成源。这可以实现吗?
  • 遗憾的是,我看到 TaskCompletionSource 不可重用。所以我应该回到自定义等待对象的想法。也许我可以创建自己的 ReusableTaskCompletionSource.
【解决方案3】:

由于您需要可以“取消设置”和“重置”的内容,因此您真正需要的是asynchronous equivalent of a manual-reset event。你可以建立自己的或use one from my AsyncEx library:

class MyClass
{
  private readonly AsyncManualResetEvent _mre = new AsyncManualResetEvent();

  public void TheMethodToAwait()
  {
    _mre.Set();
  }

  public Task TheMethodToAwaitWasExecutedAsync()
  {
    return _mre.WaitAsync();
  }

  // TODO: some code that calls _mre.Reset()
}

【讨论】:

  • 这很有趣,而且看起来很容易理解/使用。稍后我会看一下源代码,谢谢。
  • 您的实现非常优雅,但我不能使用它,因为我需要在游戏的每一帧重置事件。在 60 FPS 时,这意味着每秒重置 60 次。这将导致每秒 60 次在新的 TaskCompletionSource(内部使用的)的堆上分配。这对垃圾收集器不是很友好。如果我错了,请纠正我。
  • 我不知道你为什么会在游戏循环中使用await
  • 它不在游戏循环中。我正在尝试创建各种异步脚本来实现游戏逻辑的可能性。我需要的一个功能是,如果他们愿意,这些脚本可以通过等待下一帧的开始与游戏主循环同步。为此,主循环必须向所有等待者发送信号。使用您的课程,我需要每帧重置事件,这会引入太多分配。而且,老实说,我什至不确定是否可以使用它来完成。
猜你喜欢
  • 1970-01-01
  • 2015-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-02
  • 1970-01-01
  • 2020-12-12
相关资源
最近更新 更多