【问题标题】:Create a thread that starts multiple other threads创建一个启动多个其他线程的线程
【发布时间】:2014-01-16 15:44:30
【问题描述】:

我希望在控制器方法中从不同的端点请求数据。我只想在所有这些请求完成后返回 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();
}

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

【问题讨论】:

  • 你为什么不用 await?
  • 我对 C# 还很陌生,能否举个例子?如果可能,我希望所有请求同时完成
  • await 并不强制调用线程必须阻塞。这只是强制将方法重写为延续的编译器技巧。
  • 其实我和 Tejs 写的代码应该是单线程的。在这种情况下,您实际上需要一个并发/异步/单线程应用程序以获得最佳性能(I/O 绑定操作)。
  • 我的意思是 Brian Reischl 实际上......不是 Tejs

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


【解决方案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();
}

【讨论】:

  • 您不能使用 Select 投影到异步 lambda。 (这里甚至不需要异步 lambda,只需选择任务本身并解开对 WhenAll 的调用。)无法推断委托的类型。另外,它是DownloadStringTaskAsync 而不是DownloadStringAsyncTask
  • @Servy,你需要它来处理结果。 PSstackoverflow.com/questions/10712655/…
  • 他需要在所有操作都完成后做某事,而不是在每个操作完成时,这意味着等待或添加延续到WhenAll 的结果。如果他在每次下载完成后都完成了一些工作,那么可以肯定(尽管你仍然没有正确地做那件事),但他没有。
  • WebClient.DownloadStringAsyncTask 是一个纯函数。除非你对结果做一些事情,否则你最好不要调用它(除非目标服务器实际上使用 GET 进行操作,在这种情况下,我不对滥用 HTTP 规范的人负责)。
  • 根据他的代码,尚不清楚 OP 正在用它做什么。可能是他只对错误的结果感兴趣,也可能是他正在使用结果但没有显示它,在这种情况下,您仍然需要以一种可以编译的方式提供这样的机会。就我个人而言,我希望该方法只返回聚合结果,假设调用代码将对其进行处理。
【解决方案2】:

您需要保留对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);

【讨论】:

  • 这是同步等待所有异步操作。你不应该那样做。代码应该从上到下完全异步,或者它应该一直使用同步操作。
  • 正确,但 OP 询问如何等待线程。从他的例子来看,他也没有使用 MVC 异步控制器。
  • OP 可能已经要求它,但这仍然不是问题的正确解决方案。他不知道更好,所以解释一下为什么他不应该这样做。
  • 我们应该引导他们走上无线程异步操作的道路。
【解决方案3】:

如果您想一次触发所有请求,则不能使用简单的 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
    }
}

【讨论】:

  • Erm...你真的应该使用异步来处理错误。
  • 我只是在复制他那里的内容,因为我真的不知道他在错误处理中试图完成什么。但我更新了一个可以做到这一点的版本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-16
  • 2014-06-16
  • 1970-01-01
  • 1970-01-01
  • 2018-07-03
  • 1970-01-01
相关资源
最近更新 更多