【问题标题】:Can't start a Task with a Result when using Task constructor使用任务构造函数时无法使用结果启动任务
【发布时间】:2014-10-19 09:39:38
【问题描述】:

我有一堆任务希望能够按顺序运行。问题是,它们涉及大量磁盘读取,我需要在使用每个磁盘之间进行一些磁盘读取/写入,所以我希望能够创建一堆用于从磁盘读取的任务(并返回结果),但在我准备好之前不要启动它们。

因此,我不能使用Task.RunTask.Factory.StartNew。据我了解,这就是 Task 构造函数的用途。

例如:

public async Task<IEnumberable<Task<byte[]>>> ReadAllFiles()
{
    var folder = await ApplicationData.Current.LocalFolder;
    var files = await folder.GetFilesAsync();
    var fileTasks = files.Select(
        file => new Task<Task<byte[]>>(
            async () => {
                return (await FileIO.ReadBufferAsync(file)).ToArray();
            }).Unwrap());
    return fileTasks;
}

然后,在我的调用方法中我可以去:

var readTasks = await ReadAllFiles();
foreach(var task in readTasks)
{
    task.Start(); // Throws exception
    var bytes = await task; // If the previous line is commented out, does not return
    // Do other stuff
}

有没有办法做到这一点?现在 task.Start() 抛出异常:System.InvalidOperationException: Additional information: Start may not be called on a promise-style task.

编辑:应该注意的是,它目前的外观有些奇怪。这是因为在读取文件等每个步骤中,我在返回数据之前都会对数据进行一些额外的逻辑处理。这应该不会影响代码,只要我可以在 Task 构造方法中进行异步调用即可。

Edit2:似乎有人希望我对我的要求更清楚。

有没有一种方法可以创建具有返回值的任务,但不启动它(可能使用Task&lt;TResult&gt; constructor),以便我可以启动它并在其他时间等待该值。目前我收到了上面包含的异常。

【问题讨论】:

  • 你为什么要在这里使用Start()
  • 因此,当您使用Task.RunTask.Factory.StartNew 时,它会自动启动它创建的任务。当您使用 Task 构造函数创建任务时,它不会启动它。这意味着您必须调用Task.Start() 来启动任务,否则如果您等待它,它将永远不会返回。
  • 是的,但是你的ReadAllFiles() 返回一个惰性序列,所以即使你使用了Task.Run()Tasks 只有在你在foreach 中迭代它们时才会启动。
  • 我没有考虑过。我会试一试。所以即使它通过Task.Run返回一个Task,在枚举之前它不会真正调用Select lambda?
  • 没错,一般 LINQ 就是这样工作的。

标签: c# windows-runtime windows-phone windows-store-apps async-await


【解决方案1】:

我好像明白了。基于this answer

看来我需要保留外部 Task 的副本并单独调用 Task.Start ,然后我可以按预期返回 Unwrapped 任务。示例:

public async Task<IEnumberable<Task<byte[]>>> ReadAllFiles()
{
    var folder = await ApplicationData.Current.LocalFolder;
    var files = await folder.GetFilesAsync();
    var fileTasks = files.Select(
        file => {
            var wrappedTask = new Task<Task<byte[]>>(
            async () => {
                return (await FileIO.ReadBufferAsync(file)).ToArray();
            });

            var unwrappedTask = wrappedTask.Unwrap();
            wrappedTask.Start();
            return unwrappedTask;
        });
    return fileTasks;
}

这确保解包已完成并安排内部任务(但不启动它)。

【讨论】:

    【解决方案2】:

    Task.RunTask.Factory.StartNewTask.Task 都适用于 CPU 绑定 任务。您的任务受 I/O 限制。

    有没有一种方法可以创建一个带有返回值的任务,但不启动它......这样我就可以启动它并在另一个时间等待该值。

    当然;你想要的实际上是一个具有async-compatible 签名的委托。在这种情况下,Func&lt;Task&lt;byte[]&gt;&gt;。我有更多async 兼容代表on my blog 的示例。

    所以,你的例子可能是:

    public async Task<IEnumberable<Task<byte[]>>> ReadAllFiles()
    {
      var folder = await ApplicationData.Current.LocalFolder;
      var files = await folder.GetFilesAsync();
      var fileReaders = files.Select(file => new Func<Task<byte[]>>(
          async () => await FileIO.ReadBufferAsync(file)).ToArray()));
      return fileReaders;
    }
    
    var readers = await ReadAllFiles();
    foreach(var func in readers)
    {
      var bytes = await func();
      // Do other stuff
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-23
      • 1970-01-01
      • 2020-01-26
      • 2011-12-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多