需要有关我的用例的高效设计的建议

时间:2015-01-03 18:45:24

标签: c# wpf async-await task

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

  4. 请提供有关如何设计/实施的建议(以及一些示例代码)?我一直在阅读关于Tasks和Async / Await的大量内容,但是对于这个用例可能是最好的模式感到非常困惑?

2 个答案:

答案 0 :(得分:2)

我不能说哪种做法最好。

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

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

var myOptions = new ParallelOptions { MaxDegreeOfParallelism = 4 };

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

所以,第一个问题的答案是,你的清单非常适合完成你的任务。

回答第二个问题:,你可以。

最后一个:

在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

答案 1 :(得分:1)

正如@ 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();
  }
}