创建一个等待执行方法的等待

时间:2014-11-20 17:48:49

标签: c# async-await

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

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;
    }
}

3 个答案:

答案 0 :(得分:1)

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

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

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

答案 1 :(得分:1)

由于你需要一些可以“取消设置”和“重置”的东西,你真正需要的是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()
}

答案 2 :(得分:1)

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

实现'always-incomplete'等待的简单基类(基本上只是一系列连续的触发):

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;
    }
}

模拟课程内部就是这个(你可以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异步运行continuation。但这应该至少为如何开始提供一个框架。