这是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;当我真的想要去做你的工作并且不要打扰我。
答案 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);
}
}