C#如何在后台运行方法? (使用Task.Start而不是Async / Await)

时间:2016-07-27 12:26:39

标签: c# async-await task-parallel-library .net-4.5

这是UI代码。尝试计算listview中显示的文件和文件夹的磁盘使用情况。

                if (FileHelper.IsFolder(info))
                {
                    UsageSummary usage = DiskUsage.GetTotalUsage(info.FullName).Result;
                    totalSize += usage.TotalSize;
                }
                else
                {
                    totalSize += info.Length;
                }

这是GetTotalUsage()方法。这个想法是它应该返回一个缓存值(如果可用)。如果该值不可用,它将仅返回顶层文件的大小,并启动另一个方法,该方法将从该点递归下降目录树并创建缓存条目,以便下次访问该文件夹时我们应该获得一个缓存的值。

  async public static Task<UsageSummary> GetTotalUsage(string folder)
  {
    UsageSummary totalUsage = null;

    if (!IsCached(folder, ref totalUsage))
    {
        // Not cached, get first-level usage
        totalUsage = GetFileUsage(folder);

        // Start async process to cache total usage of this folder
        await AsyncCacheUsage(totalUsage);

    }

    // Was cached, or we got the first-level results.
    return totalUsage;
}

最后一种方法是计算文件夹实际大小的递归函数的网关方法。

   async private static Task AsyncCacheUsage(UsageSummary summary)
    {
        try
        {
            AccumulateChildFolders(summary.Folder, summary);

            CacheResults(summary);

            // Now we start a watch on the volume that 
            // can invalidate cache entries when changes occur.
            _watcher.WatchVolume(summary.Folder);

        }
        catch (Exception ex)
        {
            Log.Error(ex, "CacheTotalUsage: {1}", ex.Message);
        }
    }

代码似乎在调试器中起作用并且表现如预期的那样。

但我的问题是,我这样做了吗?对于正在显示的文件夹中的每个子文件夹,将调用一次GetTotalUsage方法,该文件夹中的许多AsynchCacheUsage实例都是文件系统的不同分支。这意味着我需要开始担心线程安全吗?所有代码都是静态的,包括访问一个静态字典,缓存我应该添加锁的用法。我还缺少其他任何陷阱?

我打电话似乎很奇怪&#34;等待AsyncCacheUsage&#34;当我真的想要去做你的工作并且不要打扰我。

2 个答案:

答案 0 :(得分:1)

  

我打电话似乎很奇怪&#34;等待AsyncCacheUsage&#34;当我真的想要去做你的工作并且不要打扰我。

这不是发布的代码会做什么。它将同步运行AsyncCacheUsage(因为没有await - 正如Damien指出的那样,编译器会给你一个AsyncCacheUsage将同步运行的警告。然后它将await,它永远不会实际控制。

您发布的代码简化为:

// No async, since there's no await
private static void AsyncCacheUsage(UsageSummary summary)
{
  try
  {
    AccumulateChildFolders(summary.Folder, summary);
    CacheResults(summary);
    _watcher.WatchVolume(summary.Folder);
  }
  catch (Exception ex)
  {
    Log.Error(ex, "CacheTotalUsage: {1}", ex.Message);
  }
}

// No async, since there's no awaits
public static UsageSummary GetTotalUsage(string folder)
{
  UsageSummary totalUsage = null;
  if (!IsCached(folder, ref totalUsage))
  {
    totalUsage = GetFileUsage(folder);
    AsyncCacheUsage(totalUsage); // No await anymore
  }
  return totalUsage;
}

if (FileHelper.IsFolder(info))
{
  UsageSummary usage = DiskUsage.GetTotalUsage(info.FullName);
  totalSize += usage.TotalSize;
}
else
{
  totalSize += info.Length;
}

换句话说,您发布的任何代码都没有异步。

  

但我的问题是,我这样做了吗?对于正在显示的文件夹中的每个子文件夹,将调用GetTotalUsage方法一次,这些实例是AsynchCacheUsage的许多实例,它们都是文件系统的不同分支。

这不是发布的代码所做的。它总是同步执行,一次一个文件夹,全部在调用线程上。

那可能就好了。大多数磁盘设备都不会在设备上传播数十或数百个同时发出的请求。

也就是说,如果你想要使这个异步,你应该从最低级别的API调用开始 - 无论你打电话来获取目录的磁盘使用情况(大概是在AccumulateChildFolders内。一旦异步,您就可以让async从那里成长。在某些时候(可能仍在AccumulateChildFolders内),您可以使用await Task.WhenAll同时处理多个文件夹(如果需要,也可以)。

答案 1 :(得分:0)

所以答案是否定的。我做得不对。 await暂停执行调用方法并将任务对象返回给原始调用者的调用对象。当等待的操作完成时,异步方法恢复它的执行。原始调用方可以通过在异步方法返回的Task对象上调用Result方法来与异步过程同步。我不想等待任何事情。我只是想开始一个后台任务。

所以我的代码应该是这样的:

在UI线程上:

                if (FileHelper.IsFolder(info))
                {
                    totalSize += DiskUsage.GetTotalUsage(info.FullName)
                }
                else
                {
                    totalSize += info.Length;
                }

GetTotalUsage方法返回缓存结果或第一级近似值,并启动将填充缓存的任务。

  public static long GetTotalUsage(string folder)
  {
    UsageSummary usage = null;

    if (!IsCached(folder, ref usage))
    {
        // Not cached, get first-level usage
        usage = GetFileUsage(folder);

        try
            {
                // Start task to FillCache for this folder.
                Log.Info("Start FillCache: {0}", folder);
                var cacheTask = new Task(() => FillCache(usage), TaskCreationOptions.LongRunning);
                cacheTask.Start();
            }
            catch (Exception ex)
            {
                DuLog.Error(ex, "!!! Run cacheTask: {0}", ex.Message);
            }

    }

    // Was cached, or we got the first-level results.
    // Race condition. FillCache() is updating usage.TotalSize.
    return usage.TotalSize;
}

FillCache使用第一级总计调用AccumulateChildFolders(递归)并缓存最终结果。中间结果会在此过程中缓存。

   private static Void FillCache(UsageSummary usage)
    {
        try
        {
            AccumulateChildFolders(summary.Folder, summary);

            CacheResults(summary);

            // Now we start a watch on the volume that 
            // can invalidate cache entries when changes occur.
            _watcher.WatchVolume(summary.Folder);

        }
        catch (Exception ex)
        {
            Log.Error(ex, "CacheTotalUsage: {1}", ex.Message);
        }
    }