这个c#异步过程可以提高性能吗?

时间:2016-07-19 08:24:27

标签: c# asynchronous

我正在开发一个程序,它会调用多个json来检索它的数据。 然而,数据非常大,在没有异步的情况下运行它需要17个小时才能完全处理。

获取数据如下:

  • 调用带有页码的服务(总共需要处理2000页),每页返回200条记录。
  • 对于它返回的每条记录,需要调用另一个服务来接收当前记录的数据。

我是整个异步功能的新手,我尝试使用async和await并且已经提升了性能但是想知道这是否是使用它的正确方法以及是否有其他方法可以增加性能

这是我目前的代码:

    static void Main(string[] args)
    {
        MainAsyncCall().Wait();
        Console.ReadKey();
    }

    public static async Task MainAsyncCall()
    {
        ServicePointManager.DefaultConnectionLimit = 999999;

        List<Task> allPages = new List<Task>();

        for (int i = 0; i <= 10; i++)
        {
            var page = i;
            allPages.Add(Task.Factory.StartNew(() => processPage(page)));
        }

        Task.WaitAll(allPages.ToArray());

        Console.WriteLine("Finished all pages");
    }

    public static async Task processPage(Int32 page)
    {
        List<Task> players = new List<Task>();
        using (var client = new HttpClient())
        {
            string url = "<Request URL>";
            var response = client.GetAsync(url).Result;
            var content = response.Content.ReadAsStringAsync().Result;
            dynamic item = Newtonsoft.Json.JsonConvert.DeserializeObject(content);

            dynamic data = item.data;
            var localPage = page;
            Console.WriteLine($"Processing Page: {localPage}");
            foreach (dynamic d in data)
            {
                players.Add(Task.Factory.StartNew(() => processPlayer(d, localPage)));
            }
        }

        Task.WaitAll(players.ToArray());
        Console.WriteLine($"Finished Page: {page}");
    }

    public static async Task processPlayer(dynamic player, int page)
    {
        using (var client = new HttpClient())
        {
            string url = "<Request URL>";
            HttpResponseMessage response = null;

            response = client.GetAsync(url).Result;

            var content = await response.Content.ReadAsStringAsync();
            dynamic item = Newtonsoft.Json.JsonConvert.DeserializeObject(content);

            Console.WriteLine($"{page}: Processed {item.name}");
        }
    }

欢迎任何建议!

1 个答案:

答案 0 :(得分:1)

这对我来说应该是这样的:

static void Main(string[] args)
{
    // it's okay here to use wait because we're at the root of the application
    new AsyncServerCalls().MainAsyncCall().Wait();

    Console.ReadKey();
}

public class AsyncServerCalls
{
// dont use static async methods
public async Task MainAsyncCall()
{
    ServicePointManager.DefaultConnectionLimit = 999999;

    List<Task> allPages = new List<Task>();

    for (int i = 0; i <= 10; i++)
    {
        var page = i;
        allPages.Add(processPage(page));
    }

    await Task.WhenAll(allPages.ToArray());

    Console.WriteLine("Finished all pages");
}

public async Task processPage(Int32 page)
{
    List<Task> players = new List<Task>();
    using (var client = new HttpClient())
    {
        string url = "<Request URL>";
        var response = await client.GetAsync(url)// nope .Result;
        var content = await response.Content.ReadAsStringAsync(); // again never use .Result;
        dynamic item = Newtonsoft.Json.JsonConvert.DeserializeObject(content);

        dynamic data = item.data;
        var localPage = page;
        Console.WriteLine($"Processing Page: {localPage}");
        foreach (dynamic d in data)
        {
            players.Add(processPlayer(d, localPage)); // no need to put the task unnecessarily on a different thread, let the current SynchronisationContext deal with that
        }
    }

    await Task.WhenAll(players.ToArray()); // always await a task in an async method
    Console.WriteLine($"Finished Page: {page}");
}

public async Task processPlayer(dynamic player, int page)
{
    using (var client = new HttpClient())
    {
        string url = "<Request URL>";
        HttpResponseMessage response = null;

        response = await client.GetAsync(url); // don't use .Result;

        var content = await response.Content.ReadAsStringAsync();
        dynamic item = Newtonsoft.Json.JsonConvert.DeserializeObject(content);

        Console.WriteLine($"{page}: Processed {item.name}");
    }
}
}

所以基本上这里的要点是确保你让SynchronisationContext做它的工作。在控制台程序中,它应该使用TaskSchedular.Default ThreadPool SynchronisationContext。您可以通过执行以下操作来强制执行此操作:

static void Main(string[] args)
{
    Task.Run(() => new AsyncServerCalls().MainAsyncCall()).Wait();
    Console.ReadKey();
}

Reference to Task.Run forcing Default

你需要记住的一件事,我上周遇到麻烦的是,你可以解决线程池的问题,即产生如此多的任务,你的进程就会因疯狂的CPU和内存使用而死亡。因此,您可能需要使用信号量来限制将要创建的线程数。

我创建了一个解决方案,可以同时处理多个部分中的单个文件Parallel Read它仍处于工作状态,但显示了async内容的使用

只是为了澄清并行性。

当您参考所有这些任务时:

allPages.Add(processPage(page));

他们都将开始。

当你这样做时:

await Task.WhenAll(allPages);

这将阻止当前的方法执行,直到所有这些页面进程都被执行(它不会阻止当前线程,不会让这些混淆)

危险区域

如果您不想阻止方法执行

Task.WhenAll

因此,您可以并行处理每个页面的所有页面流程,然后将Task添加到整个List<Task>

然而,这样做的危险就是消防......你将限制你在某个时刻执行的线程数量,所以......好吧,这取决于你,但只记得,它会发生在某些时候。