Task<Task> 在 C# 中是非法的吗?

Posted

技术标签:

【中文标题】Task<Task> 在 C# 中是非法的吗?【英文标题】:Task<Task> illegal in C#? 【发布时间】:2017-03-27 14:15:34 【问题描述】:

这段代码在VS2015中给出了一个编译时错误

错误 CS0266 无法将类型“System.Threading.Tasks.Task”隐式转换为“System.Threading.Tasks.Task”。存在显式转换(您是否缺少演员表?)

更新 1:建议在 Task 之后扩展代码而不是 Task

        int i = 0;

        Task<int> test = Task.Run( () => 
            return i;
         );

        i = test.Result;

        Task t = Task.Run( () =>   );

        Task<Task> test2 = Task.Run( () => 
            return t;
         );

        t = test2.Result;

我做错了什么?

更新 2: 这段代码给出了一个警告(我想要没有警告和禁止编译指示)

警告 CS1998 此异步方法缺少“等待”运算符,将同步运行。考虑使用 'await' 运算符来等待非阻塞 API 调用,或使用 'await Task.Run(...)' 在后台线程上执行 CPU 密集型工作。

        int i = 0;

        Task<int> test = Task.Run( () => 
            return i;
         );

        i = test.Result;

        Task t = Task.Run( () =>   ); 

        Task<Task> test2 = Task.Run( async () => 
            return t;
         );

        t = test2.Result;

更新 3:

致所有坚持Task test2的人。

    StartAndReturnSecondTaskAsync必须返回第二个任务(执行longOperation2

    StartAndReturnSecondTaskAsync 必须是 async,即 UI 在 longOperation1 期间不得阻塞

    public static async Task<Task> StartAndReturnSecondTaskAsync() 
        Task t = await Task.Run( () => 
            return StartAndReturnSecondTask();
         );
        return t;
    
    
    public static Task StartAndReturnSecondTask() 
        var importantData = longOperation1();
        return Task.Run( () => 
            longOperation2( importantData );
         );
    
    
    ...
    
    Task second = await StartAndReturnSecondTaskAsync();
    

【问题讨论】:

你确定想要 Task&lt;Task&gt;吗?你打算用它做什么? @Stephen Cleary 请参阅更新 3。 这并不能解释为什么你需要Task&lt;Task&gt; 而不仅仅是Task 我需要等待第一个任务并返回第二个 @PetSertAl 错误 CS0266 无法将类型“System.Threading.Tasks.Task”隐式转换为“System.Threading.Tasks.Task”。存在显式转换(您是否缺少演员表?) 【参考方案1】:

这是一个非常常见的但使用Task.Run 来执行一些异步操作,如果使用旧方法StartNew 它会做你所期望的,即安排一个线程池线程启动 异步操作,并告诉您异步操作何时完成正在启动

然而,这基本上不是人们真正想要的。他们想知道Task.RUn中调用的异步操作何时结束。因此,Task.Run 将解开任何 Task&lt;Task&gt; that would be returned, so that what you're really seeing is the innerTask`。

如果你真的只想让线程池线程启动任务,并且只知道它什么时候完成启动,那么您可以使用Task.Factory.StartNew,它不会代表您解开Task&lt;Task&gt;

【讨论】:

你是不是想说这是非法的,因为大多数人都太笨了,无法正确使用它?您可以使用 Task.Factory 发布工作代码吗? @AntonDuzenko 我不是说这是非法的,我是说Task.Run 为您解包任务,因为大多数使用返回Task 的方法调用Task.Run 的人会想要它展开。既然这是大多数人真正想要回来的东西,那就是它的作用。 所以我用 return await Task.Factory.StartNew() 做到了,但可以用更少的措辞来完成吗? @AntonDuzenko 当然,你没有理由做任何你实际上要求的事情。将两个长操作封装在对Task.Runawait 的调用中。然后你就完成了StartAndReturnSecondTaskAsync根本不需要存在,StartAndReturnSecondTask实际上是异步的,而不是返回一个Task而是同步完成它的一半工作,它根本没有理由这样做. @AntonDuzenko 你可以删除&lt;Task&gt;,如果你真的关心删除字符。【参考方案2】:

Task&lt;Task&gt;很常见并且经常是一个问题(如果我们错误地等待/等待错误外部任务):

  int i = 0;

  Task<int> test = Task.Run(() => 
    return i;
  );

  Task t = Task.Run(() => 
  );

  // please, notice "async"
  Task<Task> test2 = Task.Run(async () =>  // <- async: let insist on Task<Task>...
    return t;
  );

Task&lt;Task&gt; 甚至还有一个扩展方法

  Task backToTask = test2.Unwrap();

在你的情况下,你想要

  Task test2 = Task.Run(() => 
    return t;
  );

由于Task.Run 为您调用Unwrap()

【讨论】:

这可行,但会发出警告:警告 CS1998 此异步方法缺少“等待”运算符,将同步运行。考虑使用 'await' 运算符来等待非阻塞 API 调用,或使用 'await Task.Run(...)' 在后台线程上执行 CPU 密集型工作。而且我不想添加抑制杂注。 @Anton Duzenko:只需声明Task test2 = Task.Run(() =&gt; return t; );Task.Run 会为你隐式调用Unwrap Task test2 会在 t = test2.Result 上报错; @Anton Duzenko: Inner Task (t) 没有返回任何东西,这就是为什么 test2.Result (outer and unwrapped ) 也不会返回。【参考方案3】:

一个简单的解决方案,不涉及太多细节:

可行的代码是:

int i = 0;
Task<int> test = Task.Run(() =>

    return i;
);
Task t = Task.Run(() =>  );
Task test2 = Task.Run(() =>

    return t;
);

如果您使用其他可能使用await 的方法,则需要将上下文设置为async,例如:

public async Task DoesTheThreadedOperations()

// Your code in question and other code

根据您的更新 3

public static async Task StartAndReturnSecondTaskAsync()

    await Task.Run(() =>
    
        return StartAndReturnSecondTask();
    );


public static Task StartAndReturnSecondTask()

    System.Threading.Thread.Sleep(5000);
    return Task.Run(() =>
    
        System.Threading.Thread.Sleep(10000);
    );

我用 sleep 来模仿你长时间的操作。我希望你不一定想使用Task&lt;Task&gt;。这应该会给你足够的推力来攻击你的场景。您可以通过使这两个操作成为两个不同的线程并将结果从一个线程传递到另一个线程来进一步改进它以获得您的确切结果,等待您想要的一个并运行并让另一个更长的任务运行而不等待。也就是说,从您的主方法中调用这两种方法,一种是等待的,另一种不是。

【讨论】:

请参阅更新 1 @AntonDuzenko 你注意到Task&lt;Task&gt; test2 = Task.Run( Task test2 = Task.Run( 的区别了吗。不过更新 3 有点不同,让我看看。 await StartAndReturnSecondTaskAsync() 无效且不返回任务。试试这个:第二个任务 = 等待 StartAndReturnSecondTaskAsync()。我收到错误 CS0030 无法将类型“void”转换为“System.Threading.Tasks.Task”

以上是关于Task<Task> 在 C# 中是非法的吗?的主要内容,如果未能解决你的问题,请参考以下文章

为啥要在 C# 中使用 Task<T> 而不是 ValueTask<T>?

如何在 C# 中等到 Task 完成?

带有返回内容的 Task<string> 的 C# httpclient

C# Async与Await的使用

C# Task.Run()运行“含参数和返回值的方法”的用法

c#—— Task.FromResult 的使用