如何确保从taskList调用的方法不会同时执行?

时间:2019-03-20 14:39:15

标签: c# async-await task

我有一个方法,可以从cloudDrive下载一个memoryStream,然后将此memoryStream传递给taskList,该任务将读取文件:

  public async Task CreateFiles(List<MyDataClass> list)
    {
        var tasks = new List<Task>();

        foreach (var item in list)
        {
            var memorySteam = await _cloudService.DownloadStreamFromCloudAsync(item);

            tasks.Add(Task.Run(() =>
            {
                using (memoryStream)
                {
                    ReadFile readFile = new ReadFile(memoryStream, new MyFileSerializer());
                    readFile.DoWork();
                }
                UpdateTreeViewRecursively();

            }));
        }
    }

    public void UpdateTreeViewRecursively()
    {
        //...
        //Here I do recursive operations on a treeView. This may take a while
    }

问题在于,当一个任务完成并读取文件时,我调用了一个方法,该方法完成了一些耗时的工作,并且有可能该方法尚未完成,而下一个任务已在再次调用它。我应该使用锁还是针对此问题的其他解决方案?

编辑: 我将尝试解释为什么我选择了这种架构:

1)用户调用CreateFiles方法并开始从云下载:30个文件,大小从1mb到70mb不等。一次只能下载一个文件。

2)然后将每个下载的memoryStream传递到task.Add(..)。通过这样做,我希望能够同时读取很少的文件,并且在完成一个文件时,我调用UpdateTreeViewRecursively();。此方法将treeViewItem标记为可以使用,并且还检查其父级的所有其他childItem是否已下载并可以使用。这样,用户可以立即看到可以使用哪些文件,而不必等到处理了70mb的大文件后才能开始使用程序。

3)为了确保正确设置treeView中的标志,我需要确保UpdateTreeViewRecursively();在前一个调用者执行该方法时,不会再调用该方法。

2 个答案:

答案 0 :(得分:1)

  

问题在于,当一个任务完成并读取文件时,我调用了一个方法,该方法完成了一些耗时的工作,并且有可能该方法尚未完成,而下一个任务已在再次调用它。我应该使用锁还是针对此问题的其他解决方案?

由于该方法是修改UI线程,因此应在UI线程上运行。所以也许是这样的:

public async Task CreateFiles(List<MyDataClass> list)
{
  var tasks = list.Select(async item =>
  {
    using (var memorySteam = await _cloudService.DownloadStreamFromCloudAsync(item))
    {
      await Task.Run(() =>
      {
        ReadFile readFile = new ReadFile(memoryStream, new MyFileSerializer());
        readFile.DoWork();
      });
    }

    UpdateTreeViewRecursively();
  });
}

UI上下文本身充当“锁”,因此不会同时运行UpdateTreeViewRecursively

如果“这可能要花一些时间”代码使用户体验降级,则应重构该方法,以便它可以将速度较慢的部分卸载到Task.Run,或者对所有部分使用IProgress<T>其用户界面会更新,并在UpdateTreeViewRecursively中运行整个Task.Run方法。例子:

// Approach using Task.Run within UpdateTreeViewRecursively:
public async Task UpdateTreeViewRecursively()
{
  UpdateSomeTreeViewItem();
  var data = await Task.Run(() => SomeSlowCodeThatDoesNotUpdateUI());
  UpdateSomeOtherTreeViewItem(data);
}
// Approach using IProgress<T>:
public async Task CreateFiles(List<MyDataClass> list)
{
  var tasks = list.Select(async item =>
  {
    using (var memorySteam = await _cloudService.DownloadStreamFromCloudAsync(item))
    {
      await Task.Run(() =>
      {
        ReadFile readFile = new ReadFile(memoryStream, new MyFileSerializer());
        readFile.DoWork();
      });
    }

    var progress = new Progress<MyTreeViewItemUpdate>(update => UpdateSomeTreeViewItem(update));
    await Task.Run(() => UpdateTreeViewRecursively();
  });
}

public void UpdateTreeViewRecursively(IProgress<MyTreeViewItemUpdate> progress)
{
  progress?.Report(new MyTreeViewItemUpdate() { ... });
  var data = SomeSlowCodeThatDoesNotUpdateUI();
  progress?.Report(new MyTreeViewItemUpdate() { ... = data... });
}

答案 1 :(得分:0)

在Task.Run方法上使用锁或使用.Result来等待任务的结果。这样,您的主线程将等待直到任务完成。