【问题标题】:Can not get results of TaskCompletionSource无法获取 TaskCompletionSource 的结果
【发布时间】:2018-08-02 07:06:00
【问题描述】:

您好,我有以下问题:

我想执行类似于事务的操作。我想在收到外部触发器后执行许多 async 操作。因此,我使用在表示触发器的方法中设置的 TaskCompletionSourceTriggerTransaction

此触发器方法获取当我按下特定的控制台键时,在线程池中调用 Main

在我按下A 关键字后,TriggerTransaction 被执行,TaskCompletionSource-s 被设置。仍然主线程不计算两个等待任务的总和。

     class Program
                {
                    public static Task<Task<int>> TransactionOperation1()
                    {
                        TaskCompletionSource<Task<int>> tcs = new TaskCompletionSource<Task<int>>();
                        tasks.Add(tcs);
                        Task<Task<int>> result = tcs.Task;
                        return result;
                    }
                    public static Task<Task<int>> TransactionOperation2()
                    {
                        TaskCompletionSource<Task<int>> tcs = new TaskCompletionSource<Task<int>>();
                        tasks.Add(tcs);
                        Task<Task<int>> result = tcs.Task;
                        return result;
                    }

                    public static async Task<int> ExecuteTransactionOnDB()
                    {
                        await Task.Delay(1000);
                        return 5;
                    }

                    public static async Task TriggerTransaction()
                    {
                        int value = await ExecuteTransactionOnDB();
                        foreach (var item in tasks)
                        {
                            item.SetResult(value);
                        }
                    }
                    public static List<dynamic> tasks = new List<dynamic>();

                    static async Task Main(string[] args)
                    {
                        Task<Task<int>> a = TransactionOperation1();
                        Task<Task<int>> b = TransactionOperation2();
                        Task.Run(async() =>
                        {
                            while (Console.ReadKey().Key != ConsoleKey.A) ;
                            await TriggerTransaction();
                        });
                        if (!File.Exists("D:\\data.txt"))
                        {
                            File.Create("D:\\data.txt");
                        }
                        using(FileStream stream=new FileStream("data.txt",FileMode.Append,FileAccess.Write))
                        {
                        int sum=await await a + await await b;//thread wont pass this line when tasks are set.
                        ReadOnlyMemory<byte> bytes = Encoding.UTF8.GetBytes(sum);

                            stream.Write(bytes.ToArray());
                        }
                        Console.WriteLine(await await a + await await b);

                    }
                }
        }

PS 如果你想知道我为什么使用List&lt;dynamic&gt; 来存储TaskCompletionSource-s,那是因为TransactionOperations 的返回类型不同。其中一些会返回int ,其他人String..Bool..etc.

为了更好地理解,我做了一个架构- 如您所见,有:
-我要存储 TCS-es 的列表
- 一些只有在设置外部触发器后才完成的调用(事务已执行)
正如您在Calls 中看到的,都有不同的返回类型。

【问题讨论】:

  • 我想你可能误解了一些东西。很少需要创建TaskCompletionSources(除了将 EAP 异步 API 适配到 TAP 之外)。包装其他任务的任务通常也很少是正确的。不幸的是,您的大部分代码似乎都是关于做这两件事,所以我什至无法说出您要解决的问题是什么
  • @BercoviciAdrian 这看起来像XY problem。您有一个问题 X 并认为 Y 是解决方案,多个 TCS 和一个列表?当这不起作用时,你问的是 X,而不是 Y。你想要解决的真正问题是什么?可以用简单的await Task.WhenAll() 解决吗?还是一些ActionBlock&lt;T&gt; 实例?
  • 如果我有一个Task&lt;int&gt; 和一个Task&lt;bool&gt; 我如何使用Task.WhenAll 并同时获取两个结果?
  • @BercoviciAdrian 你到底想做什么?这与事务无关 - 完成或回滚的原子操作。事务与任务无关。你的问题是关于你如何尝试解决一些问题,而不是问题本身
  • 如果您想要交易,请检查 TransactionScope。如果您想实现自己的事务对象(它们被称为“资源管理器”),您需要实现IEnlistmentNotification。这个article 展示了如何做到这一点,它比听起来容易

标签: c# async-await task taskcompletionsource


【解决方案1】:

为什么需要Task&lt;Task&lt;int&gt;&gt;?只需Task&lt;int&gt; 就足够了,因此,TaskCompletionSource&lt;int&gt;。而且您还摆脱了尴尬的await await ...,这在您的情况下也不是必需的。

请注意,我还将Close() 添加到File.Create() 返回的流中。

这是该程序的工作版本:

class Program
{
    public static Task<int> TransactionOperation1()
    {
        TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
        tasks.Add(tcs);
        return tcs.Task;
    }
    public static Task<int> TransactionOperation2()
    {
        TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
        tasks.Add(tcs);
        return tcs.Task;
    }
    public static async Task<int> ExecuteTransactionOnDB()
    {
        await Task.Delay(1000);
        return 5;
    }
    public static async Task TriggerTransaction()
    {
        int value = await ExecuteTransactionOnDB();
        foreach (var item in tasks)
        {
            item.SetResult(value);
        }
    }

    public static List<dynamic> tasks = new List<dynamic>();

    static async Task Main(string[] args)
    {
        Task<int> a = TransactionOperation1();
        Task<int> b = TransactionOperation2();
        Task input = Task.Run(async () => {
            while (Console.ReadKey().Key != ConsoleKey.A);
            await TriggerTransaction();
        });
        if (!File.Exists("C:\\temp\\data.txt"))
        {
            File.Create("C:\\temp\\data.txt").Close();
        }
        using (FileStream stream = new FileStream("C:\\temp\\data.txt", FileMode.Append, FileAccess.Write))
        {
            int sum = await a + await b; // now it works ok
            var bytes = Encoding.UTF8.GetBytes(sum.ToString());
            stream.Write(bytes);
        }

        Console.WriteLine(await a + await b);
    }
}

【讨论】:

    【解决方案2】:

    查看代码的修改版本,通过执行使用TaskCompletionSource 创建的Task,它产生了预期的结果。我也将代码设为了 Generic,这样您就不需要使用 dynamic 类型并在编译时定义数据类型

    static async Task Main(string[] args)
    {
        var a = Program<int>.TransactionOperation1();
        var b = Program<int>.TransactionOperation2();
        await Task.Run(async() =>
        {
        Console.ReadLine();
        await Program<int>.TriggerTransaction(5);
        });
    
        if (!File.Exists("D:\\data.txt"))
        {
        File.Create("D:\\data.txt");
        }
    
        using (FileStream stream = new FileStream("D:\\data.txt", FileMode.Append, FileAccess.Write))
        {
            int sum = await a + await b;//thread wont pass this line when tasks are set.        
            var bytes = Encoding.UTF8.GetBytes(sum.ToString());     
            stream.Write(bytes, 0, bytes.Length);
        }
    
        Console.WriteLine(await a + await b);
    }
    
    class Program<T>
    {
        public static Task<T> TransactionOperation1()
        {
            var tcs = new TaskCompletionSource<T>();
            tasks.Add(tcs);
            return tcs.Task;
        }
    
        public static Task<T> TransactionOperation2()
        {
            var tcs = new TaskCompletionSource<T>();
            tasks.Add(tcs);
            return tcs.Task;
        }
    
        public static async Task<T> ExecuteTransactionOnDB(T t)
        {
            return await Task.FromResult(t);
        }
    
        public static async Task TriggerTransaction(T t)
        {
            T value = await ExecuteTransactionOnDB(t);
    
            foreach (var item in tasks)
            {
                item.SetResult(value);
            }
        }
    
        public static List<TaskCompletionSource<T>> tasks = new List<TaskCompletionSource<T>>();
    
    }
    

    以下是重要的修改:

    1. List&lt;dynamic&gt; 替换为 List&lt;TaskCompletionSource&lt;T&gt;&gt;
    2. TransactionOperation1/2 有返回类型Task&lt;T&gt;,这是使用TaskCompletionSource&lt;T&gt; 创建的任务
    3. Task.Run 添加了一个额外的等待,它在内部执行TriggerTransaction,但您可以替换以下代码:

       await Task.Run(async() =>
       {
         Console.ReadLine();
         await Program<int>.TriggerTransaction(5);
       });
      

      await Program&lt;int&gt;.TriggerTransaction(5);

    现在它产生了你期望的结果,它将两个整数相加。还有一些小的更改,例如删除 Task.Delay,这不是必需的

    编辑 1 - 使用 Task.WhenAll

    static async Task Main(string[] args)
    {
        var a = Program.TransactionOperation1(5);
        var b = Program.TransactionOperation1(5);
    
        Console.ReadLine();
    
        var taskResults  = await Task.WhenAll(a,b);
    
    
        dynamic finalResult = 0;
    
        foreach(var t in taskResults)
            finalResult += t;
    
    
        if (!File.Exists("D:\\data.txt"))
        {
            File.Create("D:\\data.txt");
        }
    
        using (FileStream stream = new FileStream("D:\\data.txt", FileMode.Append, FileAccess.Write))
        {
            var bytes = Encoding.UTF8.GetBytes(finalResult.ToString());
            stream.Write(bytes, 0, bytes.Length);
        }
    
        Console.WriteLine(finalResult);
    }
    
    class Program
    {
        public static Task<dynamic> TransactionOperation1(dynamic val)
        {
            return Task<dynamic>.Run(() => val);
        }
    
        public static Task<dynamic> TransactionOperation2(dynamic val)
        {
            return Task<dynamic>.Run(() => val);
        }
    
    }
    

    【讨论】:

    • 问题在于 TCS 和任务列表本身是多余的。这也使得代码的 rest 变得多余。可以用单个await Task.WhenAll() 替换所有内容吗?还是单个 ActionBlock ?
    • @PanagiotisKanavos 当然是的,我们可以使用Task.WhenAll 来执行聚合任务,使用TaskCompletionSource 并没有什么特别的实现,因为不涉及用户触发的事件,虽然我只是在修改 OP 的代码
    • @BercoviciAdrian 应该很简单,处理Task.WhenAll,在内部你会得到一个执行Task,这是Task&lt;T&gt; 的基本类型,这里你可以使用Task&lt;dynamic&gt; 并且仍然可以你需要什么操作,直到内部类型支持它
    • @BercoviciAdrian 使代码通用只是一个增值,您当然可以继续使用非通用版本并使用动态类型,直到它在内部支持您定义的 + 等操作
    • 使用Task.WhenAll查看修改后的版本(EDIT1),这肯定会减少很多复杂性并使代码更简单。此处已使用动态类型进行处理。
    猜你喜欢
    • 2018-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-16
    • 2016-03-30
    • 2013-12-07
    • 2020-04-17
    • 2021-05-15
    相关资源
    最近更新 更多