c# – Async /等待替代协同程序

前端之家收集整理的这篇文章主要介绍了c# – Async /等待替代协同程序前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我使用C#迭代器作为协同程序的替代,它一直运行良好.我想切换到异步/等待,因为我认为语法更干净,它给我类型安全.
In this (outdated) blog post,Jon Skeet shows a possible way to implement it.

我选择了稍微不同的方式(通过实现我自己的SynchronizationContext并使用Task.Yield).这工作正常

然后我意识到会有问题;目前一个协程没有必要完成运行.它可以在其产生的任何时刻优雅地停止.我们可能会有这样的代码

private IEnumerator Sleep(int milliseconds)
{
    Stopwatch timer = Stopwatch.StartNew();
    do
    {
        yield return null;
    }
    while (timer.ElapsedMilliseconds < milliseconds);
}

private IEnumerator CoroutineMain()
{
    try
    {
        // Do something that runs over several frames
        yield return Coroutine.Sleep(5000);
    }
    finally
    {
        Log("Coroutine finished,either after 5 seconds,or because it was stopped");
    }
}

协调工作通过跟踪堆栈中的所有枚举器来工作. C#编译器生成一个可以被调用的Dispose函数,以确保在CoroutineMain中正确调用’finally’块,即使枚举未完成.这样我们可以优雅地停止一个协同程序,并且仍然通过在堆栈上的所有IEnumerator对象上调用Dispose来确保finally块被调用.这基本上是手动放卷.

当我用异步/等待编写我的实现时,我意识到我们会失去这个功能,除非我错了.然后,我查找了其他协同解决方案,并且它看起来不像Jon Skeet的版本以任何方式处理它.

我可以想到的唯一办法就是拥有自己的定制’Yield’功能,这将检查协同程序是否停止,然后引发一个异常指示.这会传播起来,执行finally块,然后被抓到根部附近的某个地方.我没有找到这个,但第三方代码可能会捕获异常.

我是否误解了某些事情,这样做可能会更容易吗?或者我需要采取例外的方式来做到这一点吗?

编辑:更多信息/代码已被要求,所以这里有一些.我可以保证这只是在一个线程上运行,所以这里没有线程.
我们目前的协同实现看起来有点像这样(这是简化的,但它在这种简单的情况下工作):

public sealed class Coroutine : IDisposable
{
    private class RoutineState
    {
        public RoutineState(IEnumerator enumerator)
        {
            Enumerator = enumerator;
        }

        public IEnumerator Enumerator { get; private set; }
    }

    private readonly Stack<RoutineState> _enumStack = new Stack<RoutineState>();

    public Coroutine(IEnumerator enumerator)
    {
        _enumStack.Push(new RoutineState(enumerator));
    }

    public bool IsDisposed { get; private set; }

    public void Dispose()
    {
        if (IsDisposed)
            return;

        while (_enumStack.Count > 0)
        {
            DisposeEnumerator(_enumStack.Pop().Enumerator);
        }

        IsDisposed = true;
    }

    public bool Resume()
    {
        while (true)
        {
            RoutineState top = _enumStack.Peek();
            bool movedNext;

            try
            {
                movedNext = top.Enumerator.MoveNext();
            }
            catch (Exception ex)
            {
                // Handle exception thrown by coroutine
                throw;
            }

            if (!movedNext)
            {
                // We finished this (sub-)routine,so remove it from the stack
                _enumStack.Pop();

                // Clean up..
                DisposeEnumerator(top.Enumerator);


                if (_enumStack.Count <= 0)
                {
                    // This was the outer routine,so coroutine is finished.
                    return false;
                }

                // Go back and execute the parent.
                continue;
            }

            // We executed a step in this coroutine. Check if a subroutine is supposed to run..
            object value = top.Enumerator.Current;
            IEnumerator newEnum = value as IEnumerator;
            if (newEnum != null)
            {
                // Our current enumerator yielded a new enumerator,which is a subroutine.
                // Push our new subroutine and run the first iteration immediately
                RoutineState newState = new RoutineState(newEnum);
                _enumStack.Push(newState);

                continue;
            }

            // An actual result was yielded,so we've completed an iteration/step.
            return true;
        }
    }

    private static void DisposeEnumerator(IEnumerator enumerator)
    {
        IDisposable disposable = enumerator as IDisposable;
        if (disposable != null)
            disposable.Dispose();
    }
}

假设我们有如下代码

private IEnumerator MoveToPlayer()
{
  try
  {
    while (!AtPlayer())
    {
      yield return Sleep(500); // Move towards player twice every second
      CalculatePosition();
    }
  }
  finally
  {
    Log("MoveTo Finally");
  }
}

private IEnumerator OrbLogic()
{
  try
  {
    yield return MoveToPlayer();
    yield return MakeExplosion();
  }
  finally
  {
    Log("OrbLogic Finally");
  }
}

这将通过将OrbLogic枚举器的实例传递给Coroutine,然后运行它来创建.这允许我们每帧都勾选协程.如果玩家杀死了球,那么协奏曲没有完成;处理只是在协同程序中调用.如果MoveTo逻辑上处于“try”块,则在顶层IEnumerator上调用Dispose将在语法上使MoveTo中的finally块执行.然后,OrbLogic中的finally块将执行.
请注意,这是一个简单的案例,案例要复杂得多.

我正在努力在async / await版本中实现类似的行为.此版本的代码看起来像这样(错误检查省略):

public class Coroutine
{
    private readonly CoroutineSynchronizationContext _syncContext = new CoroutineSynchronizationContext();

    public Coroutine(Action action)
    {
        if (action == null)
            throw new ArgumentNullException("action");

        _syncContext.Next = new CoroutineSynchronizationContext.Continuation(state => action(),null);
    }

    public bool IsFinished { get { return !_syncContext.Next.HasValue; } }

    public void Tick()
    {
        if (IsFinished)
            throw new InvalidOperationException("Cannot resume Coroutine that has finished");

        SynchronizationContext curContext = SynchronizationContext.Current;
        try
        {
            SynchronizationContext.SetSynchronizationContext(_syncContext);

            // Next is guaranteed to have value because of the IsFinished check
            Debug.Assert(_syncContext.Next.HasValue);

            // Invoke next continuation
            var next = _syncContext.Next.Value;
            _syncContext.Next = null;

            next.Invoke();
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(curContext);
        }
    }
}

public class CoroutineSynchronizationContext : SynchronizationContext
{
    internal struct Continuation
    {
        public Continuation(SendOrPostCallback callback,object state)
        {
            Callback = callback;
            State = state;
        }

        public SendOrPostCallback Callback;
        public object State;

        public void Invoke()
        {
            Callback(State);
        }
    }

    internal Continuation? Next { get; set; }

    public override void Post(SendOrPostCallback callback,object state)
    {
        if (callback == null)
            throw new ArgumentNullException("callback");

        if (Current != this)
            throw new InvalidOperationException("Cannot Post to CoroutineSynchronizationContext from different thread!");

        Next = new Continuation(callback,state);
    }

    public override void Send(SendOrPostCallback d,object state)
    {
        throw new NotSupportedException();
    }

    public override int Wait(IntPtr[] waitHandles,bool waitAll,int millisecondsTimeout)
    {
        throw new NotSupportedException();
    }

    public override SynchronizationContext CreateCopy()
    {
        throw new NotSupportedException();
    }
}

我没有看到如何使用这个实现与迭代器版本类似的行为.
为长时间的代码提前道歉!

编辑2:新的方法似乎正在运作.它允许我做如下事情:

private static async Task Test()
{
    // Second resume
    await Sleep(1000);
    // Unknown how many resumes
}

private static async Task Main()
{
    // First resume
    await Coroutine.Yield();
    // Second resume
    await Test();
}

这为游戏制作AI提供了非常好的方法.

解决方法

I use C# iterators as a replacement for coroutines,and it has been
working great. I want to switch to async/await as I think the Syntax
is cleaner and it gives me type safety…

海事组织,这是一个非常有趣的问题,尽管我花了一段时间来充分了解这一点.也许,您没有提供足够的示例代码来说明这个概念.一个完整的应用程序将有所帮助,所以我将尝试填补这个空白.以下代码说明了我理解的使用模式,如果我错了,请更正我:

using System;
using System.Collections;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    // https://stackoverflow.com/q/22852251/1768303

    public class Program
    {
        class Resource : IDisposable
        {
            public void Dispose()
            {
                Console.WriteLine("Resource.Dispose");
            }

            ~Resource()
            {
                Console.WriteLine("~Resource");
            }
        }

        private IEnumerator Sleep(int milliseconds)
        {
            using (var resource = new Resource())
            {
                Stopwatch timer = Stopwatch.StartNew();
                do
                {
                    yield return null;
                }
                while (timer.ElapsedMilliseconds < milliseconds);
            }
        }

        void EnumeratorTest()
        {
            var enumerator = Sleep(100);
            enumerator.MoveNext();
            Thread.Sleep(500);
            //while (e.MoveNext());
            ((IDisposable)enumerator).Dispose();
        }

        public static void Main(string[] args)
        {
            new Program().EnumeratorTest();
            GC.Collect(GC.MaxGeneration,GCCollectionMode.Forced,true);
            GC.WaitForPendingFinalizers();
            Console.ReadLine();
        }
    }
}

在这里,Resource.Dispose被调用,因为((IDisposable)枚举器).Dispose().如果我们不调用enumerator.Dispose(),那么我们将不得不取消注释// while(e.MoveNext());并让迭代器优雅地完成,以便适当的展开.

现在,我认为用异步/等待实现的最好方法是使用custom awaiter

using System;
using System.Collections;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    // https://stackoverflow.com/q/22852251/1768303
    public class Program
    {
        class Resource : IDisposable
        {
            public void Dispose()
            {
                Console.WriteLine("Resource.Dispose");
            }

            ~Resource()
            {
                Console.WriteLine("~Resource");
            }
        }

        async Task SleepAsync(int milliseconds,Awaiter awaiter)
        {
            using (var resource = new Resource())
            {
                Stopwatch timer = Stopwatch.StartNew();
                do
                {
                    await awaiter;
                }
                while (timer.ElapsedMilliseconds < milliseconds);
            }
            Console.WriteLine("Exit SleepAsync");
        }

        void AwaiterTest()
        {
            var awaiter = new Awaiter();
            var task = SleepAsync(100,awaiter);
            awaiter.MoveNext();
            Thread.Sleep(500);

            //while (awaiter.MoveNext()) ;
            awaiter.Dispose();
            task.Dispose();
        }

        public static void Main(string[] args)
        {
            new Program().AwaiterTest();
            GC.Collect(GC.MaxGeneration,true);
            GC.WaitForPendingFinalizers();
            Console.ReadLine();
        }

        // custom awaiter
        public class Awaiter :
            System.Runtime.CompilerServices.INotifyCompletion,IDisposable
        {
            Action _continuation;
            readonly CancellationTokenSource _cts = new CancellationTokenSource();

            public Awaiter()
            {
                Console.WriteLine("Awaiter()");
            }

            ~Awaiter()
            {
                Console.WriteLine("~Awaiter()");
            }

            public void Cancel()
            {
                _cts.Cancel();
            }

            // let the client observe cancellation
            public CancellationToken Token { get { return _cts.Token; } }

            // resume after await,called upon external event
            public bool MoveNext()
            {
                if (_continuation == null)
                    return false;

                var continuation = _continuation;
                _continuation = null;
                continuation();
                return _continuation != null;
            }

            // custom Awaiter methods
            public Awaiter GetAwaiter()
            {
                return this;
            }

            public bool IsCompleted
            {
                get { return false; }
            }

            public void GetResult()
            {
                this.Token.ThrowIfCancellationRequested();
            }

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

            // IDispose
            public void Dispose()
            {
                Console.WriteLine("Awaiter.Dispose()");
                if (_continuation != null)
                {
                    Cancel();
                    MoveNext();
                }
            }
        }
    }
}

当我们放松时,我要求在Awaiter内取消订单.将状态机移动到下一步(如果有待处理的延续).这导致观察Awaiter.GetResult(由编译器生成代码调用)中的取消.这会抛出TaskCanceledException,并进一步展开using语句.所以资源得到妥善处理.最后,任务转换到取消状态(task.IsCancelled == true).

IMO,这是比当前线程安装自定义同步上下文更简单直接的方法.它可以轻松适应多线程(更多细节here).

这应该比IEnumerator / yield有更多的自由.您可以在协同逻辑中使用try / catch,您可以通过Task对象直接观察异常,取消和结果.

更新,AFAIK在迭代器生成的IDispose中没有类推,当涉及到异步状态机时.当你想取消/解开它时,你真的要驱动状态机结束.如果你想考虑一些疏忽使用try / catch来阻止取消,我认为最好的方法是检查Awaiter.Cancel(MoveNext之后)中的_continuation是否为非空,并抛出致命异常out-of-the-band(使用助手async void方法).

猜你在找的C#相关文章