【问题标题】:C# how to run a method in the background? (use Task.Start not Async/Await)C#如何在后台运行一个方法? (使用 Task.Start 而不是 Async/Await)
【发布时间】:2016-12-01 11:06:42
【问题描述】:

这是用户界面代码。正在尝试计算列表视图中显示的文件和文件夹的磁盘使用情况。

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

当我真正的意思是出去做你的工作而不打扰我时,我调用“await AsyncCacheUsage”似乎很奇怪。

【问题讨论】:

  • 我认为您应该收到AsyncCacheUsage 的警告。你,你是否选择忽略它?
  • AsyncCacheUsage is doing an await 中没有任何内容,因此它将阻塞其线程。 AccumulateChildFolders 可能也应该是异步的,但是您需要小心更新作为参数传递的对象(我假设您使用结果更新 summary 参数)因为这将在 之后更新函数已返回。最好使用返回值。
  • 异步不是在上面的 GetTotalUsage 中开始的吗?这就是我打算开始的地方。我没有从编译器收到“您的方法将同步运行”警告。

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


【解决方案1】:

当我真正的意思是出去做你的工作而不打扰我时,我调用“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 调用开始 - 无论您调用什么来获取目录的磁盘使用情况(大概在 @ 987654326@)。一旦这是异步的,那么您可以让async 从那里增长。在某些时候(可能仍在AccumulateChildFolders 内),您可以使用await Task.WhenAll 来同时处理多个文件夹(如果您愿意,可以再次处理)。

【讨论】:

  • 第一个过程中有一个等待。这就是异步开始的地方。也许是这个名字让你失望。 AsyncCacheUsage 可以尽可能多地阻塞。目的是 GetTotal 使用应该返回缓存的结果或第一级近似值。并让缓存构建过程逐渐消失。我使用它是一个进程,因为它确实是一个繁重的等待 [原文如此] 任务,我想触发并忘记它。
  • 我想你可能对async 的作用感到困惑。如果没有await,它将不会是异步的。正如我所说,当你使用 async 而不使用 await 时,编译器会给你一个警告,因为这几乎肯定是一个错误。
  • 对。我让警告消失了,但我仍然没有完全按照我的想法去做。我知道等待我不想等待的事情可能是正确的。我听从了警告并转换为任务/开始。
【解决方案2】:

所以答案是否定的。我做得不对。 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);
        }
    }

【讨论】:

  • new TaskTask.Start 永远不应使用。你的旧代码(使用Task.Run)更好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-10
  • 2019-08-17
  • 2019-12-01
  • 1970-01-01
  • 2012-03-20
相关资源
最近更新 更多