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

本文关键字:Start Async Await Task 使用 后台 运行 方法 | 更新日期: 2023-09-27 17:58:47

这是UI代码。正在尝试计算列表视图中显示的文件和文件夹的磁盘使用率。

                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的许多实例都在文件系统的不同分支中递减。这意味着我需要开始担心线程安全性?所有的代码都是静态的,包括访问一个静态字典,该字典缓存了我可能应该添加锁的用法。我还缺少其他陷阱吗?

我的意思是去做你的工作,不要打扰我,而我却称之为"等待AsyncCacheUsage",这似乎很奇怪。

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

这似乎很奇怪,我称之为"等待AsyncCacheUsage",而我真正的意思是去做你的工作,不要打扰我。

发布的代码不会这么做。它将同步运行AsyncCacheUsage(因为没有awaits——正如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同时处理多个文件夹(如果需要的话)。

所以答案是否定的。我做得不对。wait挂起调用方法的执行,并将任务对象返回给原始调用者的调用对象。当等待的操作完成时,异步方法将继续执行。原始调用方可以通过调用异步方法返回的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);
        }
    }