Reputation: 1817
I am trying to integrate a third party library in my WPF application. This library is used to collect credit card history for a person by his/her social security number. It basically provides a method that returns void. Once that method is complete, you can collect credit card history.
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;
Now I want to run this API for number of persons simultaneously, however, I also want to have a threshold of maximum number of requests at a time. If that threshold is hit, I would like to wait for one of those request to complete before sending a new request. Finally I would like provide an update my UI as any one of submitted requests succeeds or fails.
Here are my questions:
This third party API may end up waiting for quite sometime for collecting the information. Is using TPL/PLINQ APIs like Parallel.ForEach
or AsParallel
good candidate for this situation?
Can I use await/async
to create a wrapper around calls to third party library given that its Run method returns a void?
What would be best approach to wait for individual request submitted so that I can keep updating my UI about the progress.
Please provide your suggestions (with some sample code) on how it should be designed/implemented? I have been doing lot of reading on Tasks and Async/Await but got quite confused as what could be best patterns for this use case?
Upvotes: 0
Views: 127
Reputation: 456322
As @l3arnon pointed out, your API is synchronous (even though it's performing I/O-based operations, which are naturally asynchronous). So, you'll need to treat it as blocking.
Without knowing more about your requirements, I'd recommend async
with Task.Run
. You could also use Parallel
or Parallel LINQ (AsParallel
), but they work best when you know the total number of operations at the beginning, and it's not clear whether that's the case in this scenario. Also, if you have to add operations while others are in-flight, you could also consider TPL Dataflow
and Rx
. IMO, the async
approach has the lowest learning curve. However, Rx in particular has a very elegant design and once you learn it you'll start using it everywhere.
First off, you'd probably want a utility method that uses natural return types and exceptions for error handling. I'm not sure why your third-party class doesn't already have something like this:
CreditHistory GetCreditHistory(string ssn)
{
var instance = new Instance(ssn);
instance.Run();
if (instance.Success)
return instance.CreditHistory;
throw ...
}
In the async
case, you'd start off writing a method that should be called from the UI thread, and executes the work in the background:
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.
}
}
Then you can add asynchronous throttling. This is one approach, using the built-in 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();
}
}
Upvotes: 1
Reputation: 5935
I cannot say which of the practices are best of the best.
You can use sample from MSDN about the Degree of Parallelism. But this will require to install the System.Threading.Tasks.Dataflow namespace.
The Parallel class has the option, where you can specify the degree of the parallelism, too:
var myOptions = new ParallelOptions { MaxDegreeOfParallelism = 4 };
Parallel.For(0, NumberOfClients, myOptions, i=>
{
// Code to run
});
So, the answer on the first question is YES, your list is very good candidate to accomplish your task.
Answering the second: yes, you can.
The last one:
On UI Thread you can call something like this:
Task.Factory.StartNew(DoWork);
You can take use CancellationToken in order to support cancellation. You also can use other overloads of the method, in order to obtain required behavior. You also can have the Value property, which will denote how much records was proceeded. This property you can bound with ProgressBar, and update it properly
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");
});
}
Few words about Interlocked.Increment(ref value): it performs increment of the specified variable and store the result, as an atomic operation, in order to prevent the data race condition.
Upvotes: 2