创建一个启动多个其他线程的线程

时间:2014-01-16 15:44:31

标签: c# asp.net-mvc asynchronous concurrency

我希望在控制器方法中请求来自不同端点的数据。我只想在完成所有这些请求后返回View()。可以这样做,怎么做呢?

现在我正在做一些接近这个的事情

class GetDemData
{
    int count = 0;
    int requestsCompleted = 0;

    List<string> addresses = new List<string>();

    public void AddDataToBeCollected(string address)
    {
        adresses.Add(address);
    }

    public void CollectData()
    {
        foreach (string address in addresses)
        {
            HttpClient client = new HttpClient();
            client.BaseAddress = new Uri("http://localhost:1337/");
            client.GetAsync(address).ContinueWith(
                getTask =>
                    {
                        if (getTask.IsCanceled)
                        {
                            error();
                        }
                        else if(getTask.IsFaulted)
                        {
                            error();
                        }
                        else
                        {
                            requestsCompleted++;
                            checkFinished();
                        }
                    }
            );
        }
    }

    public void checkFinished()
    {
        if (count == requestsCompleted)
        {
            // All data collected
        }
    }

    public void error()
    {
        // yes error
    }
}

这是我的控制器

public ActionResult GetData()
{
    var data = new GetDemData();
    // fill data with addresses
    data.CollectData();

    return View();
}

问题是,由于所有内容都是异步完成的,因此会立即返回View。如何确保仅在收集所有数据时返回视图?

3 个答案:

答案 0 :(得分:1)

public class GetDemData
{

    List<string> addresses = new List<string>();

    public void AddDataToBeCollected(string address)
    {
        adresses.Add(address);
    }

    public Task CollectData()
    {
                var webclient = new WebClient();
                var tasks = from address in addresses
                            select webclient.DownloadStringTaskAsync(address);

                return Task.WhenAll(tasks.Select(
                            async (downloadTask) => 
                            {
                                 var result = await downloadTask;
                                 //Do somthing with result
                            }));
    }

}


public async Task<ActionResult> GetData()
{
    var data = new GetDemData();
    // fill data with addresses
    await data.CollectData();

    return View();
}

答案 1 :(得分:0)

您需要保留对Task实例的引用,然后在该任务上调用.Wait()以强制线程阻塞,直到完成这些请求。例如:

var mainThread = Task.Factory.StartNew(() =>
      {
             var tasks = addresses.Select(x => Task.Factory.StartNew(() =>
                 {
                       // Do your download stuff for one address

                       return "someContent";
                 });

            Task.WaitAll(tasks); // Blocks all the minor tasks, waits until they all complete

            return tasks.Select(x => x.Result).ToList(); // You may observe exceptions here
      });

var allResults = mainThread.Result; // List<string>, blocks until all tasks are complete
                                    // can also observe exceptions here

return View(allResults);

答案 2 :(得分:0)

如果要一次触发所有请求,则无法使用简单的async / await。例如,如果您在循环中使用await,它仍然会一次触发一个请求,等待它完成,然后触发下一个请求。相反,您必须触发所有请求,收集生成的Task对象,并等待所有这些对象完成。

public Task CollectData()
{
    var tasks = new List<Task>();
    foreach (string address in addresses)
    {
        HttpClient client = new HttpClient();
        client.BaseAddress = new Uri("http://example.com:1337/");

        //Note we're collecting the resulting Task objects here
        //We're actually getting the task from the continuation, which is a little bit weird
        //Alternatively, you could break this into another method that uses await internally
        var task = client.GetAsync(address).ContinueWith(
            getTask =>
                {
                    if (getTask.IsCanceled)
                    {
                        error();
                    }
                    else if(getTask.IsFaulted)
                    {
                        error();
                    }
                    else
                    {
                        requestsCompleted++;
                        checkFinished();
                    }
                }
        );
        tasks.Add(task);
    }

    //Return a single task that completes when all the subtasks are done
    return Task.WhenAll(tasks.ToArray());
}

...控制器

public async Task<ActionResult> GetData()
{
    var data = new GetDemData();
    // fill data with addresses
    var task = data.CollectData();
    await task;
    return View();
}

通过使用仅触发一个请求的单独方法来更新,这是一种更简洁的方法。此外,我刚刚注意到您没有正确使用HttpClient - 它是IDisposable并且还包含一个内部请求池,因此应该重复使用它,而不是按请求创建和销毁它。

public async Task CollectData()
{
    var tasks = new List<Task>();
    using (var client = new HttpClient())
    {
        foreach (string address in addresses)
        {
            tasks.Add(ExecuteSingleRequest(client, address));
        }

        await Task.WhenAll(tasks.ToArray());
    }
}

private async Task ExecuteSingleRequest(HttpClient client, Uri uri)
{
    try
    {
        var response = await client.GetAsync(uri);
    }
    catch (Exception ex)
    {
        //This is lazy example code, do real error handling here and don't catch Exception
    }
}