需要为我的用例提供高效设计的建议

本文关键字:高效 我的 | 更新日期: 2023-09-27 18:35:59

我正在尝试在我的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。

请就如何设计/实现它提供您的建议(带有一些示例代码)?我一直在阅读大量关于任务和异步/等待的文章,但对这个用例的最佳模式是什么感到非常困惑?

需要为我的用例提供高效设计的建议

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

可以使用 MSDN 中有关并行度的示例。但这需要安装 System.Threading.Tasks.Dataflow 命名空间。

并行

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

var myOptions = new ParallelOptions { MaxDegreeOfParallelism = 4 };
Parallel.For(0, NumberOfClients, myOptions, i=>
{ 
    // Code to run
});

因此,第一个问题的答案是肯定的,您的列表非常适合完成您的任务。

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

最后一个:

在 UI 线程上,您可以调用如下所示的内容:

Task.Factory.StartNew(DoWork);

您可以使用取消令牌来支持取消。还可以使用该方法的其他重载,以获取所需的行为。您还可以具有 Value 属性,该属性将指示处理了多少记录。此属性可以与进度条绑定,并正确更新它

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值)的几句话:它执行指定变量的增量并将结果存储为原子操作,以防止数据竞争条件。

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

在不了解您的要求的情况下,我建议您async Task.Run.您也可以使用 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();
  }
}