【问题标题】:Need suggestions for efficient design for my use case需要针对我的用例进行高效设计的建议
【发布时间】:2015-03-01 17:41:42
【问题描述】:

我正在尝试在我的 WPF 应用程序中集成第三方库。该库用于通过他/她的社会安全号码收集一个人的信用卡历史记录。它基本上提供了一个返回 void 的方法。一旦该方法完成,您就可以收集信用卡历史记录。

ThirdPartyLibrary Instance = new Instance(SSN);
Instance.Run();    // This method returns a void and it blocks until data retrieval completes
if ( Instance.Success )
    CreditHistory History = Instance.CreditHistory;

现在我想针对人数同时运行此 API,但是,我还想设置一次最大请求数的阈值。如果达到该阈值,我想等待其中一个请求完成,然后再发送新请求。最后,我想在任何一个提交的请求成功或失败时更新我的​​ UI。

这是我的问题:

  1. 此第三方 API 可能最终会等待相当长的时间来收集信息。在这种情况下使用 TPL/PLINQ API 像 Parallel.ForEachAsParallel 是很好的选择吗?

  2. 我可以使用await/async 来创建对第三方库的调用的包装器,因为它的 Run 方法返回一个 void?

  3. 等待个人请求提交的最佳方法是什么,以便我可以不断更新我的 UI 以了解进度。

请就应该如何设计/实现它提供您的建议(以及一些示例代码)?我一直在阅读有关 Tasks 和 Async/Await 的大量内容,但对于这个用例的最佳模式可能是什么感到很困惑?

【问题讨论】:

  • 您可以使用上限为msdn.microsoft.com/en-us/library/dd267301(v=vs.110).aspx的阻塞集合
  • 感谢 Balm 的建议。这将用于使用生产者-消费者模式,对吧?如果是这样,我也读过它,但我不确定如何将它与这个第三方库集成,同时还能获得进展的更新。有什么想法吗?
  • 我大部分时间都在阅读/使用较小的代码示例来理解这些概念。例如,我在几个地方读到 Parallel.ForEach 不适合任何执行 I/O 的操作。这个第三方 API 调用了一些用于数据检索的网络服务,所以我只是想了解在我的设计中应该真正使用哪种方法
  • @PaulSnow API 是async
  • @PaulSnow 如果 API 被阻塞,那么您在这件事上别无选择,因为 API 不是 asyncParallel.ForeachAsParallelBlockingCollection 和 TPL Dataflow 都是有效选项。

标签: c# wpf async-await task


【解决方案1】:

我不能说哪种做法是最好的。

您可以使用来自 MSDN 的关于 Degree of Parallelism 的示例。但这需要安装 System.Threading.Tasks.Dataflow 命名空间。

Parallel 类具有选项,您也可以在其中指定并行度:

var myOptions = new ParallelOptions { MaxDegreeOfParallelism = 4 };

Parallel.For(0, NumberOfClients, myOptions, i=>
{ 
    // Code to run
});

所以,第一个问题的答案是YES,您的列表非常适合完成您的任务。

回答第二个:是的,你可以。

最后一个:

在 UI 线程上,您可以这样调用:

Task.Factory.StartNew(DoWork);

您可以使用CancellationToken 来支持取消。您还可以使用该方法的其他重载,以获得所需的行为。您还可以拥有 Value 属性,它表示处理了多少记录。这个属性可以绑定ProgressBar,并正确更新

public void DoWork()
{
    var myOptions = new ParallelOptions { MaxDegreeOfParallelism = 4 };

    Parallel.For(0, NumberOfClients, myOptions, i=>
    { 
        ThirdPartyLibrary Instance = new Instance(SSN[i]);
        Instance.Run();
        Interlocked.Increment(ref value);
        RaisePropertyChanged("Value");
    });
}

关于Interlocked.Increment(ref value)的几句话:它执行指定变量的增量并将结果存储为原子操作,以防止the data race condition

【讨论】:

  • 谢谢 stukselbax。尝试将 await 与 Instance.Run 一起使用时出现编译错误,抱怨其无效。你说可以用。我错过了什么?
  • 可能你应该将你的阻塞方法Instance.Run()包装到方法中,该方法将被标记为async并与await关键字一起使用。
【解决方案2】:

正如@l3arnon 所指出的,您的 API 是同步的(即使它正在执行基于 I/O 的操作,这些操作自然是异步的)。因此,您需要将其视为阻塞。

在不了解您的要求的情况下,我推荐asyncTask.Run。您也可以使用Parallel 或Parallel LINQ (AsParallel),但是当您知道一开始的操作总数时,它们的效果最好,并且不清楚在这种情况下是否是这种情况。此外,如果您必须在其他操作正在进行时添加操作,您还可以考虑TPL DataflowRx。 IMO,async 方法的学习曲线最低。但是,特别是 Rx 的设计非常优雅,一旦你学会了它,你就会开始在任何地方使用它。

首先,您可能需要一个使用自然返回类型和异常处理错误的实用方法。我不确定为什么你的第三方类还没有这样的东西:

CreditHistory GetCreditHistory(string ssn)
{
  var instance = new Instance(ssn);
  instance.Run();
  if (instance.Success)
    return instance.CreditHistory;
  throw ...
}

async 的情况下,您将开始编写一个应该从 UI 线程调用的方法,并在后台执行工作:

async Task GetCreditHistoryOnBackgroundThreadAsync(string ssn)
{
  try
  {
    var history = await Task.Run(() => GetCreditHistory(ssn));
    // Update UI with credit history.
  }
  catch (Exception ex)
  {
    // Update UI with error details.
  }
}

然后您可以添加异步限制。这是一种方法,使用内置的SemaphoreSlim

private readonly SemaphoreSlim _mutex = new SemaphoreSlim(10);
async Task GetCreditHistoryOnBackgroundThreadAsync(string ssn)
{
  await _mutex.WaitAsync();
  try
  {
    var history = await Task.Run(() => GetCreditHistory(ssn));
    // Update UI with credit history.
  }
  catch (Exception ex)
  {
    // Update UI with error details.
  }
  finally
  {
    _mutex.Release();
  }
}

【讨论】:

  • 感谢您的回复。我会知道此应用程序将发送多少次信用卡历史检索请求。它如何更改您在上面提供的代码示例?我也喜欢这个想法,所以如果代码发生重大变化,我会要求在你的答案中添加它而不删除现有的。
  • @PaulSnow:您可以只使用await Task.WhenAll 来执行多个并发操作。
  • 使用 await Task.WhenAll,UI 将被阻塞,直到所有任务完成。在这种情况下,如何在收到个人信用记录结果时更新 UI?
  • @PaulSnow:不,await Task.WhenAll 不会阻止 UI。
  • 所以使用WhenAll,我的代码将在所有任务完成后运行。当单个任务完成时如何更新 UI?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-13
  • 2013-01-10
  • 1970-01-01
  • 2013-11-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多