c# - 在完成后长时间保留 Task 对象有什么缺点吗?

标签 c# .net design-patterns task-parallel-library c#-5.0

我发现自己养成了将 Task 对象保存为结果容器的习惯。

到目前为止,我还没有发现任何缺点,而且我发现代码比使用单独的变量来存储任务完成后的结果更清晰。

下面是几个用法示例。虽然我不认为它真的相关,但它们已作为 MVVM 应用程序中 View 模型的一部分。 (请注意,这不是实际的工作代码,我只是尝试勾勒出模式。)

  • 早期初始化

    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;
    

    我看不到任何好处,只会增加复杂性。

  • 后台 worker

    一个更复杂的示例使用任务来处理图像,创建一个包含重新调整大小的图像的新文件。必须生成包含这些图像的报告;它通过它们的路径访问图像文件,并且它必须尽可能使用重新调整大小的版本——如果不是,则使用原始图像。因此,我需要能够将原始图像的路径映射到它们调整大小后的版本。

    // 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.

我的担忧如下:

  1. 这是一个好的使用模式吗?
  2. 它有什么缺点或陷阱吗? (例如内存泄漏、潜在的死锁、锁定的池资源等)
  3. 我是否应该将任务的结果解包并直接存储(因此,在我看来,这会增加代码的复杂性)?

最佳答案

  1. 如果它能为您带来更干净、更清晰的代码,那么是的,这是一个很好的模式。
  2. 唯一真正潜在的缺点是这些任务不会被垃圾收集器收集。只要您使用此模式几次而不是数千次或数百万次,这对您来说可能永远不会成为问题*(不用担心死锁或线程池资源,因为完成的任务就是这样,已完成。它永远不会有另一个线程处理它,因为 tasks are not reusable )。
  3. 只要内存不会成为问题,就没有理由这样做。

* 另一个缺点是,如果您多次await 错误任务,您每次都会抛出异常。这可能会有问题,但这取决于你具体的异常处理。

关于c# - 在完成后长时间保留 Task 对象有什么缺点吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25931580/

相关文章:

c# - 在 .Net 中改进 String.Insert?

c# - File.WriteAllLines 不要写 æ, ø 字母

c# - 将许多变量写入文本文件

design-patterns - 忘记密码 模式或技术? ¿ 模式名称?

c# - 获取列表中的后续元素 C#

c# - 使用 linq 聚合文本文件内容,以便将它们分组

.net - 如何获取调用进程 Windows 用户访问 token

c# - 由于错误而无法删除临时文件

android - 是否有任何设计模式可以应用于 Android 位置管理器帮助程序类?

javascript - OOP 中 Controller 类常见吗