一尘不染

任务排序和重新进入

c#

我有以下情况,我认为这可能很常见:

  1. 有一个任务(UI命令处理程序)可以同步或异步完成。

  2. 命令到达的速度可能快于处理速度。

  3. 如果已经有命令的待处理任务,则应将新的命令处理程序任务排队并按顺序处理。

  4. 每个新任务的结果可能取决于先前任务的结果。

应该注意取消,但是为了简单起见,我想将其保留在此问题的范围之外。另外,线程安全性(并发性)不是必需的,但必须支持重入。

这是我要实现的基本示例(为简单起见,作为控制台应用程序):

using System;
using System.Threading.Tasks;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var asyncOp = new AsyncOp<int>();

            Func<int, Task<int>> handleAsync = async (arg) =>
            {
                Console.WriteLine("this task arg: " + arg);

                //await Task.Delay(arg); // make it async

                return await Task.FromResult(arg); // sync
            };

            Console.WriteLine("Test #1...");
            asyncOp.RunAsync(() => handleAsync(1000));
            asyncOp.RunAsync(() => handleAsync(900));
            asyncOp.RunAsync(() => handleAsync(800));
            asyncOp.CurrentTask.Wait();

            Console.WriteLine("\nPress any key to continue to test #2...");
            Console.ReadLine();

            asyncOp.RunAsync(() =>
            {
                asyncOp.RunAsync(() => handleAsync(200));
                return handleAsync(100);
            });

            asyncOp.CurrentTask.Wait();
            Console.WriteLine("\nPress any key to exit...");
            Console.ReadLine();
        }

        // AsyncOp
        class AsyncOp<T>
        {
            Task<T> _pending = Task.FromResult(default(T));

            public Task<T> CurrentTask { get { return _pending; } }

            public Task<T> RunAsync(Func<Task<T>> handler)
            {
                var pending = _pending;
                Func<Task<T>> wrapper = async () =>
                {
                    // await the prev task
                    var prevResult = await pending;
                    Console.WriteLine("\nprev task result:  " + prevResult);
                    // start and await the handler
                    return await handler();
                };

                _pending = wrapper();
                return _pending;
            }
        }

    }
}

输出:

测试#1 ...

上一个任务结果:0
此任务参数:1000

上一个任务结果:1000
此任务arg:900

上一个任务结果:900
此任务arg:800

按任意键继续测试#2 ...


上一个任务结果:800

上一个任务结果:800
此任务arg:200
此任务arg:100

按任何一个键退出...

它会按照要求工作,直到在测试2中引入可重入性为止:

asyncOp.RunAsync(() =>
{
    asyncOp.RunAsync(() => handleAsync(200));
    return handleAsync(100);
});

所需的输出应该是100200而不是200100因为有已经是一个悬而未决外任务100。这显然是因为内部任务同步执行,从而破坏var pending = _pending; /* ... */ _pending = wrapper()了外部任务的逻辑。

如何使其也适用于测试2?

一种解决方案是使用强制每个任务执行异步Task.Factory.StartNew(..., TaskScheduler.FromCurrentSynchronizationContext()。但是,我不想在可能内部同步的命令处理程序上强加异步执行。另外,我也不想依赖于任何特定同步上下文的行为(即,Task.Factory.StartNew在实际开始创建的任务之前,依赖于它的返回)。

在现实生活的项目中,我负责AsyncOp上述工作,但无法控制命令处理程序(即内部的任何内容handleAsync)。


阅读 273

收藏
2020-05-19

共1个答案

一尘不染

我几乎忘记了可以Task手动构建而不需要启动或调度它。然后,“ Task.Factory.StartNew”与“ new
Task(…)。Start”
使我回到正轨。我认为这是Task<TResult>构造函数实际上可能有用的少数几种情况之一,还有嵌套的任务(Task<Task<T>>)和Task.Unwrap()

// AsyncOp
class AsyncOp<T>
{
    Task<T> _pending = Task.FromResult(default(T));

    public Task<T> CurrentTask { get { return _pending; } }

    public Task<T> RunAsync(Func<Task<T>> handler, bool useSynchronizationContext = false)
    {
        var pending = _pending;
        Func<Task<T>> wrapper = async () =>
        {
            // await the prev task
            var prevResult = await pending;
            Console.WriteLine("\nprev task result:  " + prevResult);
            // start and await the handler
            return await handler();
        };

        var task = new Task<Task<T>>(wrapper);
        var inner = task.Unwrap();
        _pending = inner;

        task.RunSynchronously(useSynchronizationContext ?
            TaskScheduler.FromCurrentSynchronizationContext() :
            TaskScheduler.Current);

        return inner;
    }
}

输出:

测试#1 ...

上一个任务结果:0
此任务参数:1000

上一个任务结果:1000
此任务arg:900

上一个任务结果:900
此任务arg:800

按任意键继续测试#2 ...


上一个任务结果:800
此任务arg:100

上一个任务结果:100
此任务arg:200

现在,如果需要,AsyncOp可以通过添加一个lockto保护来使线程安全变得非常容易_pending

2020-05-19