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",这似乎很奇怪。
这似乎很奇怪,我称之为"等待AsyncCacheUsage",而我真正的意思是去做你的工作,不要打扰我。
发布的代码不会这么做。它将同步运行AsyncCacheUsage
(因为没有await
s——正如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);
}
}