Call webservice asynchronously and wait for all th

2020-04-20 08:16发布

问题:

I need to call the web service several times to get data, and then put those data into my database, so I've got the following code:

foreach (string v in options)
{
    IList<MySampleNode> nodes = _pi.GetData(v);
    _dbService.SaveToDb(nodes);
}

the GetData implementation looks as follows:

public IList<MySampleNode> GetData(string v)
{
    IList<MySampleNode> nodes = null;
    try
    {
        var client = new WsClient();
        IEnumerable<IWsObject> wsNodes = client.getNodes(new getClassLevel { code = v });
        nodes = ProcessData(wsNodes);
    }
    return nodes;
}

I'd like to modify this code to asynchronous version to run each download/save to database in separate thread and wait for all threads to be finished, or maybe there are some other approaches to improve the performance of such code, can you please help me?

回答1:

To improve the scalability (i.e., the number of request your web app can serve simultaneously), you need to reduce the number of threads your app is using for each requests. So, instead of waiting for all threads to be finished, you should use naturally asynchronous APIs, which don't block a thread while the operation is pending. More details on this can be found here.

If you can use .NET 4.5, your specific case might be improved like this:

public void Page_Load(object sender, EventArgs e)
{
    RegisterAsyncTask(new PageAsyncTask(ProcessDataAsync));
}

public async Task ProcessDataAsync()
{
    var tasks = options.Select(v => _pi.GetDataAsync(v));
    await Task.WhenAll(tasks);
    var nodes = tasks.Select(t => t.Result);
    _dbService.SaveToDb(nodes);
}

public async Task<IList<MySampleNode>> GetDataAsync(string v)
{
    IList<MySampleNode> nodes = null;
    using (var client = new WsClient())
    {
        IEnumerable<IWsObject> wsNodes = 
            await client.getNodesAsync(new getClassLevel { code = v });
        nodes = ProcessData(wsNodes);
    }
    return nodes;
}

The client-side proxy for WsClient probably already has an async version of getNodes, called getNodesAsync (if not, check this). ProcessDataAsync starts a bunch of parallel non-blocking GetDataAsync tasks (for each node), and asynchronously awaits their completion. That's what makes this code scale well.

You might be able to further improve ProcessDataAsync by saving the data asynchronously, i.e. await _dbService.SaveToDbAsync(nodes), if you can leverage the use of asynchronous Task-based DB API (e.g, with EF6).



回答2:

This downloads all in parallel and stores them into the DB with one write.

var tasks = options.Select(o => Task.Run(() => GetData(o)));
Task.WaitAll(tasks.ToArray());
var nodes = tasks.SelectMany(t => t.Result);
_dbService.SaveToDb(nodes);


回答3:

This would initiate asynchronous call

 public delegate IList<MySampleNode> SaveToDb(IList<MySampleNode> myParam);
 SaveToDb _saveToDb= new SaveToDb(objService.SaveToDb);
 IAsyncResult asyncSearchResult = saveToDb.BeginInvoke(input,null,null)

This would wait for execution to complete and return value:-

IList<MySampleNode> result=asyncSearchResult EndInvoke();