c# – 在完成后长时间保持Task对象有什么缺点吗?
作者:互联网
我发现自己养成了将Task对象远远超出完成容器的习惯.
到目前为止,我还没有发现任何缺点,我发现代码比在任务完成后使用单独的变量来存储结果更清晰.
下面是几个使用示例.虽然我认为它不是真正相关的,但它们已经成为MVVM应用程序中View Models的一部分.
(请注意,这不是实际的工作代码,我只是尝试概述模式.)
>早期初始化
SlowClient是一些需要几秒钟才能连接到WCF或REST的类
服务.因此,我通过任务尽快初始化它.当需要客户端时,等待任务,产生初始化的SlowClient(如果任务完成或等待完成后立即生成).
所以我会做这样的事情:
public class SomeViewModel
{
private Task<SlowClient> _slowClientInitializerTask;
public SomeViewModel()
{
_slowClientInitializerTask = Task.Run(() => CreateAndInitSlowClient());
}
private SlowClient CreateAndInitSlowClient()
{
// SlowClient instantiation and initialization, taking a few seconds...
}
// Task result consumer example
void OnSomeCommandExecuted(object parameter)
{
try
{
var client = await _slowClientInitializerTask;
// do something with the client
} catch {
// may re-create the task if the client ceases to be valid
}
}
}
如OnSomeCommandExecuted所示,使用SlowClient的每个方法都将执行:
var client = await _slowClientInitializerTask;
如果由于某种原因结果不再有效(
连接到被删除的服务或什么不是,我只是运行一个新的
任务并将其分配给_slowClientInitializerTask – 就像在
构造函数中显示的代码.
此模式的替代方法是创建一些额外的_slowClient变量,该变量在任务完成后更新,因此每次要使用时都需要进行检查.例如:
if (_slowClient == null)
_slowClient = await _slowClientInitializerTask;
我认为没有任何好处,只会增加复杂性.
>背景工作者
更复杂的示例使用任务来处理图像,创建包含重新调整大小的图像的新文件.必须生成包含这些图像的报告;它通过路径访问图像文件,并且必须尽可能使用重新调整大小的版本 – 如果不是,则使用原始图像.因此,我需要能够将原始图像的路径映射到它们的重新调整大小的版本.
// Key: original image path; Value: task returning the re-sized image path
private Dictionary<string, Task<string>> _resizeTasks;
// Costly operation => Want to execute it asynchronously
private string ResizeImage(string originalImagePath)
{
// returns the path of a temporary resized image file
}
// Command execution handler for instance => Launches image resize on background
void OnAddImageExecuted(object parameter)
{
string path = parameter as string;
if (!_resizeTasks.Keys.Contains(path))
_resizeTasks[path] = Task.Run(() => ResizeImage(path));
}
// Generates a report consuming the images => Requires the result of the tasks
void OnGenerateReportExecuted(object parameter)
{
try {
foreach (var faulted in from entry in _resizeTasks
where entry.Value.IsFaulted select entry.Key)
_resizeTasks[path] = Task.Run(() => ResizeImage(path)); // Retry
await Task.WhenAll(_resizeTasks.Values); // Wait for completion
} catch { } // Ignore exceptions thrown by tasks (such as I/O exceptions)
var imagePaths = _resizeTasks[path].Select(entry =>
entry.Value.Status == TaskStatus.RanToCompletion ?
entry.Value.Result : entry.Key);
// generate the report requiring the image paths
}
实际实现使用ConcurrentDictionary,因为图像上的添加是异步执行的.此外,可以删除图像并再次添加,因此当前添加的图像有一个单独的列表,_resizeTasks也可用作先前重新调整大小的图像的缓存.
任务处理不是这里的主题,因为我可以在以后处理它们,无论如何,在这些情况下似乎没有必要,如the Do I need to dispose of Tasks? post from Parallel Programming with .NET MSDN Blog所述:
No. Don’t bother disposing of your tasks, not unless performance or scalability testing reveals that you need to dispose of them based on your usage patterns in order to meet your performance goals. If you do find a need to dispose of them, only do so when it’s easy to do so, namely when you already have a point in your code where you’re 100% sure that they’re completed and that no one else is using them.
我的担忧如下:
>这是一个很好的使用模式吗?
>它有任何缺点或陷阱吗? (例如,内存泄漏,潜在的死锁,锁定的池资源等)
>相反,我应该从任务中解包结果并直接存储(因此,在我看来,增加代码复杂性)?
解决方法:
>如果它为您带来更清晰,更清晰的代码,那么它是一个很好的模式.
>唯一真正的潜在缺点是垃圾收集器无法收集这些任务.只要您使用此模式几次而不是数千次或数百万次,这对您来说可能永远不会成为问题*(不用担心死锁或线程池资源,因为完成的任务就是完成的任务.将永远不会有另一个线程处理它,因为tasks are not reusable).
>只要记忆不成问题,就没有理由这样做.
*另一个缺点是,如果您多次等待故障任务,则每次都会抛出异常.这可能有问题,但这取决于您的特定异常处理.
标签:c,net,design-patterns,c-5-0,task-parallel-library 来源: https://codeday.me/bug/20190628/1320262.html