C# 此模式是否具有与node.js类似的生命周期,是否存在任何问题?

C# 此模式是否具有与node.js类似的生命周期,是否存在任何问题?,c#,queue,async-await,nonblocking,worker,C#,Queue,Async Await,Nonblocking,Worker,我正在熟悉C#中的async关键字,以及图书馆生态系统,以寻求一个实用的解决方案 我的研究得出以下静态主循环代码: Task loop = Task.Factory.StartNew(async () => { try { using(RedisConnection redis = new RedisConnection("localhost")) { var queue = "my_queue"; var res

我正在熟悉C#中的
async
关键字,以及图书馆生态系统,以寻求一个实用的解决方案

我的研究得出以下静态主循环代码:

Task loop = Task.Factory.StartNew(async () => {

    try {
        using(RedisConnection redis = new RedisConnection("localhost")) {

            var queue = "my_queue";
            var reserved = string.Concat(queue, "_reserved");

            redis.Open();

            while(true) {

                Task.Factory.StartNew(async () => {

                    var pushRequest = await redis.Lists.RemoveLastAndAddFirstString(0, queue, reserved);

                });

            }

        }
    }
    catch(Exception ex) {
        Console.Error.WriteLineAsync(ex.Message);
    }

}, cancellationToken);

loop.Wait();
如您所见,我正在尝试创建一个非阻塞工作程序。我觉得我已经接近了核心循环代码本身的完整解决方案,很明显,内部
Task.Factory.StartNew
调用中的内容将开始转移到单独的类和方法中

我有几个问题:

  • 我现在在这里的任何内容都应该从静态main方法中移出吗
  • 我注意到,当我运行这段代码时,我的CPU活动增加了一点,我假设是因为我正在创建多个连续体,请求redis提供信息。这是正常的还是我应该减轻这一点
  • 在这一点上,这真的是异步/非阻塞的吗?我的内部lambda中的代码不会占用我的主工作线程吗
  • C#是否为continuations生成额外的线程

为可能出现的业余错误道歉。正如我所提到的,我仍在学习,但我希望从社区获得最后一点指导。

您不需要额外的池线程来执行IO绑定操作(关于这方面的更多想法)。在这种情况下,重新分解的代码可能如下所示:

async Task AsyncLoop(CancellationToken cancellationToken)
{
    using (RedisConnection redis = new RedisConnection("localhost"))
    {

        var queue = "my_queue";
        var reserved = string.Concat(queue, "_reserved");

        redis.Open();

        while (true)
        {
            // observe cancellation requests
            cancellationToken.ThrowIfCancellationRequested();

            var pushRequestTask = redis.Lists.RemoveLastAndAddFirstString(0, queue, reserved);

            // continue on a random thread after await, 
            // thanks to ConfigureAwait(false)
            var pushRequest = await pushRequestTask.ConfigureAwait(false);

            // process pushRequest
        }
    }
}

void Loop(CancellationToken cancellationToken)
{
    try
    {
        AsyncLoop().Wait();
    }
    catch (Exception ex)
    {
        while (ex is AggregateException && ex.InnerException != null)
            ex = ex.InnerException;

        // might be: await Console.Error.WriteLineAsync(ex.Message),
        // but you cannot use await inside catch, so: 
        Console.Error.WriteLine(ex.Message);
    }
}
注意,缺少
Task.Run
/
Task.Factory.StartNew
并不意味着总是会有相同的线程。在本例中,由于
ConfigureAwait(false)
,因此
await
之后的代码很可能会在不同的线程上继续。但是,如果调用线程上没有同步上下文(例如,对于控制台应用程序),则此
ConfigureAwait(false)
可能是多余的。要了解更多信息,请参阅中列出的文章,特别是Stephen Cleary的文章

可以在
AsyncLoop
中复制Node.js事件循环的单线程、线程仿射行为,因此
wait
之后的每个延续都在同一线程上执行。您将使用自定义任务计划程序(或自定义
SynchronizationContext
TaskScheduler.FromCurrentSynchronizationContext
)。这并不难实现,尽管我不确定这是否是您所要求的

如果这是一个服务器端应用程序,在同一个线程a-la Node.js上序列化
await
continuations可能会损害应用程序的可伸缩性(特别是如果在
awaits
之间有一些CPU限制的工作)

要解决您的具体问题:

我现在在这里的任何东西都应该从静态中移出吗 主要方法

如果您使用的是一系列的
async
方法,那么在最顶层的堆栈框架中将有一个异步到-
任务的转换。对于控制台应用程序,可以在
Main
中使用
Task.Wait()
阻止,这是最上面的入口点

我注意到当我运行这段代码时,我的CPU活动增加了一点,我 假设因为我正在创建多个continuations请求redis 供参考。这是正常的还是我应该减轻这一点

很难说清楚为什么你的CPU活动会增加。我宁愿责怪这个应用程序的服务器端,它在
localhost
上侦听和处理redis请求

在这一点上,这真的是异步/非阻塞的吗 在我的内部lambda从来没有绑住我的主要工作线程

如果
new RedisConnection
是非阻塞的,并且
RemoveLastAndAddFirstString
不会阻塞其同步部分内的任何地方,那么我发布的代码确实是非阻塞的

C#是否为continuations生成额外的线程


不明确。异步IO操作处于“运行中”时没有线程。但是,当操作完成时,将有一个来自
ThreadPool
的IOCP线程被分配来处理完成。
wait
之后的代码是否在此线程或原始线程上继续,取决于同步上下文。如前所述,可以使用
ConfigureAwait(false)
控制此行为。

您将永远不断生成新的侦听器。这不是一个好主意。你能建议我如何或在哪里解决这个问题,以便它只尝试在获得价值后工作吗?非常好的信息,非常感谢!我认为还值得一提的是,我认为虽然节点是“单线程的”,但我所读到的信息表明,它只是一个从不阻塞的工作者。其他操作有时可能会产生一个新线程,它只是主逻辑,永远不会被阻塞。这可能会使您在此建议的代码与node.js方法几乎相同?另外,请注意。AsyncLoop方法是否应该在某处返回任务?@Omega,事实上,只有在任何地方有阻塞块(例如,同步DNS解析)时,工作进程才会阻塞。每当发生这种情况并且没有合适的自然异步API可供使用时,您可以使用
wait Task.Run()
包装此类调用。所以
AsyncLoop
类似于节点的事件循环,除了
await
continuations没有线程关联之外<代码>异步循环
不必显式返回任何内容,因为存在
异步任务
签名(
异步
缺失,已修复)。C编译器使其返回一个
任务
。如果没有
async
,它将不得不这样做,但您不能在它里面使用
wait
。非常感谢您的帮助,这里非常清晰。@Omega,它是安全的。CPU和内存的使用实际上取决于“RemovelastandDdfirstString
内部的情况。尝试
等待任务。改为延迟(500)`并比较您的里程数。