Parallel.ForEach中每个线程的DbContext是否安全?

时间:2019-09-04 22:49:25

标签: c# multithreading entity-framework parallel-processing entity-framework-6

我现在正在签订合同,以提高使用EF 6作为ORM的现代SaaS SPA Web应用程序的后端服务的性能。我建议的第一件事是向当前运行单线程的后端服务引入一些多线程。首席软件工程师表示我们不能这样做,因为EF 6不是线程安全的。

我不是实体框架专家。我选择的ORM是DevExpress的XPO,并且使用该ORM,我所做的事情与下面建议的类似。使用EF 6,这种模式本质上是不安全的吗?

// In .ts file
import { DomSanitizer} from '@angular/platform-browser';

eulaContent = '';

constructor(private sanitizer: DomSanitizer)

ngOnInit(){
  fetch('/assets/yourlocation.html').then(res => res.text()).then(data => {
    this.eulaContent = this.sanitizer.bypassSecurityTrustHtml(data);
  })
}

我已经对此进行了研究,并且我同意DbContext不是线程安全的。我提出的模式确实使用了多个线程,但是单个线程只能以单线程方式访问单个DbContext。线索告诉我DbContext本质上是一个单例,而该代码最终将使数据库混乱。我找不到任何证据来支持这一主张。主管是正确的吗?

谢谢

2 个答案:

答案 0 :(得分:1)

您的模式是线程安全的。但是,至少对于SQL Server,如果并发性太高,您会发现随着数据库资源争用的增加,总吞吐量会下降。

从理论上讲,Parallel.ForEach优化了线程数,但实际上,我发现它允许应用程序中的并发过多。

您可以使用ParallelOptions可选参数来控制并发性。测试您的用例,看看默认并发是否适合您。

您的评论:请记住,现在无论如何,我们在上面的代码中谈论的是100个ID,其中大多数ID代表的工作不会导致对数据库的任何更改且寿命短整手操作可能需要几分钟才能完成,并最终将10的新记录添加到数据库中。您会建议什么MaxDegreesOfParallelism值?

根据您的一般描述,大概为2-3,但这取决于数据库密集型ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords的性能(与执行CPU密集型活动或等待文件,Web服务调用等的IO)有关。除此之外,如果该方法主要执行数据库任务,则很可能会导致锁定争用或使IO子系统(磁盘)不堪重负。确保在您的环境中进行测试。

也许值得探索为什么ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords花费这么长时间才能完成给定ID。

更新

这里有一些测试代码来演示线程之间不会相互阻塞,而是实际上并发运行。为了简单起见,我删除了DbContext部分,因为它不会影响线程问题。

class SomeTaskProcessor
{
    static Random rng = new Random();
    public int Id { get; private set; }
    public SomeTaskProcessor(int id) { Id = id; }
    public void ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords()
    {
        Console.WriteLine($"Starting ID {Id}");
        System.Threading.Thread.Sleep(rng.Next(1000));
        Console.WriteLine($"Completing ID {Id}");
    }
}
class Program
{
    static void Main(string[] args)
    {
        int[] ids = Enumerable.Range(1, 100).ToArray();

        Parallel.ForEach(ids, id => {
                var processor = new SomeTaskProcessor(id);
                processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
        });
    }
}

答案 1 :(得分:0)

问题1-内存分配

每一次迭代都会创建DbContext的实例,这将导致内存分配。垃圾收集器将需要处理这些分配。如果内存压力持续不断,最终将导致应用程序级别的性能下降。

问题2-SQL重载

基于以上所述,在每次迭代中,您将调用SaveChanges(),这可能会调用数据库。如果对数据库的调用会占用大量资源,则可能会导致数据库性能不佳。

问题#3-线程阻塞

SaveChanges()是同步的,将阻塞线程。在对数据库的调用需要大量时间的情况下,线程将坐下等待。与第三方API调用相同。性能不会提高或提高很小。

问题#4-Parallel.For *方法不能保证并行运行

  

请务必牢记,Parallel.For,Parallel.ForEach或ForAll循环中的各个迭代可能但不必并行执行。

我认为这是不言自明的。

Potential Pitfalls with PLINQ

Understanding Speedup in PLINQ


我正在阅读有关该问题的评论,而Eric J.回答恐怕该问题的解决方法存在问题,并且并行性将无济于事。

  

感谢您的回答Eric。请记住,现在无论如何,我们在上面的代码中谈论的是100个ID,其中大多数ID代表的工作不会以数据库的任何更改而告终,并且短暂运行而满手操作可能需要几分钟才能完成并最终将10的新记录添加到数据库中。您会建议什么MaxDegreesOfParallelism值?

     

啊-是的,我应该包括这个细节。在这种情况下,正在执行的任务非常受IO限制,需要调出第三方Web API来收集所需的信息以进行处理。收集完所有数据后,将对其进行处理,并可能导致需要将一些新记录添加到数据库中。在处理阶段,有一些查询可以收集一些其他信息,但是所有这些都运行良好。

如果我没有完全错,问题是同步调用第三方API,这会阻塞线程。

我认为异步方法可能有助于从第三方API收集数据。

如果不需要的话,也将有助于不分配堆内存。您只需执行一个DbContext实例和一个数据库调用即可在执行结束时保存更改。


更新

Eric J.添加了测试,以测试线程彼此之间没有阻塞(并没有阻塞)并测试并发执行。下面的代码是他在答案中提供的原始代码。

class SomeTaskProcessor
{
    static Random rng = new Random();
    public int Id { get; private set; }
    public SomeTaskProcessor(int id) { Id = id; }
    public void ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords()
    {
        Console.WriteLine($"Starting ID {Id}");
        System.Threading.Thread.Sleep(rng.Next(1000));
        Console.WriteLine($"Completing ID {Id}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        int[] ids = Enumerable.Range(1, 100).ToArray();

        Parallel.ForEach(ids, id => {
                var processor = new SomeTaskProcessor(id);
                processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
        });
    }
}

我做了类似的测试。我创建了服务器来模拟Internet IO,延迟响应为1秒。在启动每个测试运行API服务器并发出第一个请求之前。测试是在Release config中运行的。

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase {
    // GET api/values
    [HttpGet]
    public async Task<ActionResult<IEnumerable<string>>> Get() {
        await Task.Delay(1000);

        return new string[] { "value1", "value2" };
    }
}

我已经更新了他的代码以调用API端点。在我的虚拟机上,它旋转了5个线程,平均执行时间为23秒。

class SomeTaskProcessor {
    private HttpClient http = new HttpClient() {
        BaseAddress = new Uri("http://localhost:49890/api/values")
    };

    public int Id { get; private set; }
    public SomeTaskProcessor(int id) { Id = id; }
    public void ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords() {
        Console.WriteLine($"Starting ID {Id}. Thread Id: {Thread.CurrentThread.ManagedThreadId}");
        var response = http.GetAsync(String.Empty).GetAwaiter().GetResult();
        Console.WriteLine($"Completing ID {Id}. Response status code is {response.StatusCode}. Thread Id: {Thread.CurrentThread.ManagedThreadId}");
    }
}

class Program {
    static void Main(string[] args) {
        int[] ids = Enumerable.Range(1, 100).ToArray();;

        var stopwatch = new Stopwatch();

        stopwatch.Start();

        Parallel.ForEach(ids, id => {
            var processor = new SomeTaskProcessor(id);
            processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
        });

        // ~23 seconds

        stopwatch.Stop();

        Console.WriteLine(stopwatch.Elapsed.ToString());
    }
}

控制台输出:

Starting ID 51. Thread Id: 4
Starting ID 1. Thread Id: 1
Starting ID 2. Thread Id: 5
Starting ID 52. Thread Id: 9
Starting ID 3. Thread Id: 12
Completing ID 51. Response status code is OK. Thread Id: 4
Starting ID 53. Thread Id: 4
Completing ID 2. Response status code is OK. Thread Id: 5
Starting ID 4. Thread Id: 5
Starting ID 55. Thread Id: 14
Starting ID 6. Thread Id: 15
Completing ID 52. Response status code is OK. Thread Id: 9
Starting ID 56. Thread Id: 9
Completing ID 1. Response status code is OK. Thread Id: 1
Starting ID 7. Thread Id: 1
Completing ID 3. Response status code is OK. Thread Id: 12
Starting ID 9. Thread Id: 12
Starting ID 58. Thread Id: 16
Completing ID 53. Response status code is OK. Thread Id: 4
Starting ID 54. Thread Id: 4
Completing ID 4. Response status code is OK. Thread Id: 5
Starting ID 5. Thread Id: 5
Starting ID 11. Thread Id: 18
Completing ID 55. Response status code is OK. Thread Id: 14
Starting ID 59. Thread Id: 14
Starting ID 61. Thread Id: 20
Completing ID 56. Response status code is OK. Thread Id: 9
Starting ID 57. Thread Id: 9
Completing ID 7. Response status code is OK. Thread Id: 1
Starting ID 8. Thread Id: 1
Completing ID 9. Response status code is OK. Thread Id: 12
Starting ID 10. Thread Id: 12
Completing ID 6. Response status code is OK. Thread Id: 15
Starting ID 12. Thread Id: 15
Starting ID 14. Thread Id: 22
Completing ID 5. Response status code is OK. Thread Id: 5
Starting ID 15. Thread Id: 5
Completing ID 54. Response status code is OK. Thread Id: 4
Starting ID 62. Thread Id: 4
Completing ID 58. Response status code is OK. Thread Id: 16
Starting ID 66. Thread Id: 16
Starting ID 68. Thread Id: 23
Completing ID 11. Response status code is OK. Thread Id: 18
Starting ID 19. Thread Id: 18
Completing ID 59. Response status code is OK. Thread Id: 14
Starting ID 60. Thread Id: 14
Starting ID 21. Thread Id: 24
Completing ID 57. Response status code is OK. Thread Id: 9
Starting ID 69. Thread Id: 9
Completing ID 12. Response status code is OK. Thread Id: 15
Starting ID 13. Thread Id: 15
Completing ID 61. Response status code is OK. Thread Id: 20
Starting ID 73. Thread Id: 20
Completing ID 10. Response status code is OK. Thread Id: 12
Completing ID 8. Response status code is OK. Thread Id: 1
Starting ID 22. Thread Id: 12
Starting ID 26. Thread Id: 1
Starting ID 75. Thread Id: 25
Completing ID 15. Response status code is OK. Thread Id: 5
Starting ID 16. Thread Id: 5
Completing ID 62. Response status code is OK. Thread Id: 4
Starting ID 63. Thread Id: 4
Completing ID 66. Response status code is OK. Thread Id: 16
Starting ID 67. Thread Id: 16
Completing ID 14. Response status code is OK. Thread Id: 22
Starting ID 30. Thread Id: 22
Starting ID 32. Thread Id: 26
Starting ID 76. Thread Id: 27
Completing ID 60. Response status code is OK. Thread Id: 14
Starting ID 77. Thread Id: 14
Completing ID 19. Response status code is OK. Thread Id: 18
Starting ID 20. Thread Id: 18
Completing ID 69. Response status code is OK. Thread Id: 9
Starting ID 70. Thread Id: 9
Completing ID 21. Response status code is OK. Thread Id: 24
Starting ID 33. Thread Id: 24
Completing ID 13. Response status code is OK. Thread Id: 15
Starting ID 35. Thread Id: 15
Completing ID 73. Response status code is OK. Thread Id: 20
Starting ID 74. Thread Id: 20
Completing ID 22. Response status code is OK. Thread Id: 12
Starting ID 23. Thread Id: 12
Completing ID 26. Response status code is OK. Thread Id: 1
Starting ID 27. Thread Id: 1
Completing ID 68. Response status code is OK. Thread Id: 23
Starting ID 81. Thread Id: 23
Starting ID 39. Thread Id: 29
Completing ID 67. Response status code is OK. Thread Id: 16
Starting ID 83. Thread Id: 16
Completing ID 75. Response status code is OK. Thread Id: 25
Completing ID 30. Response status code is OK. Thread Id: 22
Starting ID 31. Thread Id: 22
Completing ID 16. Response status code is OK. Thread Id: 5
Starting ID 87. Thread Id: 25
Starting ID 17. Thread Id: 5
Completing ID 63. Response status code is OK. Thread Id: 4
Starting ID 64. Thread Id: 4
Starting ID 89. Thread Id: 30
Completing ID 76. Response status code is OK. Thread Id: 27
Starting ID 90. Thread Id: 27
Completing ID 32. Response status code is OK. Thread Id: 26
Starting ID 40. Thread Id: 26
Completing ID 77. Response status code is OK. Thread Id: 14
Starting ID 78. Thread Id: 14
Starting ID 42. Thread Id: 31
Completing ID 70. Response status code is OK. Thread Id: 9
Starting ID 71. Thread Id: 9
Completing ID 27. Response status code is OK. Thread Id: 1
Starting ID 28. Thread Id: 1
Completing ID 74. Response status code is OK. Thread Id: 20
Starting ID 92. Thread Id: 20
Completing ID 35. Response status code is OK. Thread Id: 15
Starting ID 36. Thread Id: 15
Completing ID 81. Response status code is OK. Thread Id: 23
Starting ID 82. Thread Id: 23
Completing ID 33. Response status code is OK. Thread Id: 24
Starting ID 34. Thread Id: 24
Completing ID 20. Response status code is OK. Thread Id: 18
Starting ID 43. Thread Id: 18
Completing ID 23. Response status code is OK. Thread Id: 12
Starting ID 24. Thread Id: 12
Starting ID 96. Thread Id: 32
Completing ID 39. Response status code is OK. Thread Id: 29
Completing ID 17. Response status code is OK. Thread Id: 5
Starting ID 18. Thread Id: 5
Starting ID 47. Thread Id: 29
Completing ID 64. Response status code is OK. Thread Id: 4
Starting ID 65. Thread Id: 4
Completing ID 87. Response status code is OK. Thread Id: 25
Starting ID 88. Thread Id: 25
Completing ID 31. Response status code is OK. Thread Id: 22
Completing ID 83. Response status code is OK. Thread Id: 16
Starting ID 84. Thread Id: 16
Starting ID 49. Thread Id: 22
Starting ID 97. Thread Id: 33
Completing ID 90. Response status code is OK. Thread Id: 27
Starting ID 91. Thread Id: 27
Completing ID 40. Response status code is OK. Thread Id: 26
Starting ID 41. Thread Id: 26
Completing ID 89. Response status code is OK. Thread Id: 30
Starting ID 98. Thread Id: 30
Completing ID 78. Response status code is OK. Thread Id: 14
Starting ID 79. Thread Id: 14
Starting ID 100. Thread Id: 34
Completing ID 36. Response status code is OK. Thread Id: 15
Starting ID 37. Thread Id: 15
Completing ID 92. Response status code is OK. Thread Id: 20
Starting ID 93. Thread Id: 20
Completing ID 42. Response status code is OK. Thread Id: 31
Completing ID 28. Response status code is OK. Thread Id: 1
Starting ID 29. Thread Id: 1
Completing ID 24. Response status code is OK. Thread Id: 12
Starting ID 25. Thread Id: 12
Completing ID 82. Response status code is OK. Thread Id: 23
Completing ID 71. Response status code is OK. Thread Id: 9
Starting ID 72. Thread Id: 9
Completing ID 43. Response status code is OK. Thread Id: 18
Starting ID 44. Thread Id: 18
Completing ID 96. Response status code is OK. Thread Id: 32
Completing ID 65. Response status code is OK. Thread Id: 4
Completing ID 88. Response status code is OK. Thread Id: 25
Completing ID 49. Response status code is OK. Thread Id: 22
Starting ID 50. Thread Id: 22
Completing ID 47. Response status code is OK. Thread Id: 29
Starting ID 48. Thread Id: 29
Completing ID 18. Response status code is OK. Thread Id: 5
Completing ID 34. Response status code is OK. Thread Id: 24
Completing ID 84. Response status code is OK. Thread Id: 16
Starting ID 85. Thread Id: 16
Completing ID 97. Response status code is OK. Thread Id: 33
Completing ID 41. Response status code is OK. Thread Id: 26
Completing ID 79. Response status code is OK. Thread Id: 14
Starting ID 80. Thread Id: 14
Completing ID 100. Response status code is OK. Thread Id: 34
Completing ID 93. Response status code is OK. Thread Id: 20
Starting ID 94. Thread Id: 20
Completing ID 29. Response status code is OK. Thread Id: 1
Completing ID 98. Response status code is OK. Thread Id: 30
Starting ID 99. Thread Id: 30
Completing ID 44. Response status code is OK. Thread Id: 18
Starting ID 45. Thread Id: 18
Completing ID 25. Response status code is OK. Thread Id: 12
Completing ID 37. Response status code is OK. Thread Id: 15
Starting ID 38. Thread Id: 15
Completing ID 72. Response status code is OK. Thread Id: 9
Completing ID 91. Response status code is OK. Thread Id: 27
Completing ID 50. Response status code is OK. Thread Id: 22
Completing ID 48. Response status code is OK. Thread Id: 29
Completing ID 85. Response status code is OK. Thread Id: 16
Starting ID 86. Thread Id: 16
Completing ID 80. Response status code is OK. Thread Id: 14
Completing ID 94. Response status code is OK. Thread Id: 20
Starting ID 95. Thread Id: 20
Completing ID 99. Response status code is OK. Thread Id: 30
Completing ID 45. Response status code is OK. Thread Id: 18
Starting ID 46. Thread Id: 18
Completing ID 38. Response status code is OK. Thread Id: 15
Completing ID 86. Response status code is OK. Thread Id: 16
Completing ID 95. Response status code is OK. Thread Id: 20
Completing ID 46. Response status code is OK. Thread Id: 18
00:00:23.6046580

C:\Program Files\dotnet\dotnet.exe (process 7208) exited with code 0.
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .

然后,我创建了相同代码的异步版本。执行平均需要7秒钟。

class SomeTaskProcessor {
    private HttpClient http = new HttpClient() {
        BaseAddress = new Uri("http://localhost:49890/api/values")
    };

    public int Id { get; private set; }
    public SomeTaskProcessor(int id) { Id = id; }
    public async Task ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords() {
        Console.WriteLine($"Starting ID {Id}");
        var response = await http.GetAsync(String.Empty);
        Console.WriteLine($"Completing ID {Id}. Response status code is {response.StatusCode}");
    }
}

class Program {
    static async Task Main(string[] args) {
        int[] ids = Enumerable.Range(1, 100).ToArray();;

        var stopwatch = new Stopwatch();

        stopwatch.Start();

        var tasks = ids.Select(id => {
            var processor = new SomeTaskProcessor(id);
            return processor.ExecuteLongRunningProcessThatReadsDbAndCreatesSomeNewRecords();
        }).ToArray();

        await Task.WhenAll(tasks);

        // ~8 seconds

        stopwatch.Stop();

        Console.WriteLine(stopwatch.Elapsed.ToString());
    }
}

控制台输出:

Starting ID 1. Thread Id: 1
Starting ID 2. Thread Id: 1
Starting ID 3. Thread Id: 1
Starting ID 4. Thread Id: 1
Starting ID 5. Thread Id: 1
Starting ID 6. Thread Id: 1
Starting ID 7. Thread Id: 1
Starting ID 8. Thread Id: 1
Starting ID 9. Thread Id: 1
Starting ID 10. Thread Id: 1
Starting ID 11. Thread Id: 1
Starting ID 12. Thread Id: 1
Starting ID 13. Thread Id: 1
Starting ID 14. Thread Id: 1
Starting ID 15. Thread Id: 1
Starting ID 16. Thread Id: 1
Starting ID 17. Thread Id: 1
Starting ID 18. Thread Id: 1
Starting ID 19. Thread Id: 1
Starting ID 20. Thread Id: 1
Starting ID 21. Thread Id: 1
Starting ID 22. Thread Id: 1
Starting ID 23. Thread Id: 1
Starting ID 24. Thread Id: 1
Starting ID 25. Thread Id: 1
Starting ID 26. Thread Id: 1
Starting ID 27. Thread Id: 1
Starting ID 28. Thread Id: 1
Starting ID 29. Thread Id: 1
Starting ID 30. Thread Id: 1
Starting ID 31. Thread Id: 1
Starting ID 32. Thread Id: 1
Starting ID 33. Thread Id: 1
Starting ID 34. Thread Id: 1
Starting ID 35. Thread Id: 1
Starting ID 36. Thread Id: 1
Starting ID 37. Thread Id: 1
Starting ID 38. Thread Id: 1
Starting ID 39. Thread Id: 1
Starting ID 40. Thread Id: 1
Starting ID 41. Thread Id: 1
Starting ID 42. Thread Id: 1
Starting ID 43. Thread Id: 1
Starting ID 44. Thread Id: 1
Starting ID 45. Thread Id: 1
Starting ID 46. Thread Id: 1
Starting ID 47. Thread Id: 1
Starting ID 48. Thread Id: 1
Starting ID 49. Thread Id: 1
Starting ID 50. Thread Id: 1
Starting ID 51. Thread Id: 1
Starting ID 52. Thread Id: 1
Starting ID 53. Thread Id: 1
Starting ID 54. Thread Id: 1
Starting ID 55. Thread Id: 1
Starting ID 56. Thread Id: 1
Starting ID 57. Thread Id: 1
Starting ID 58. Thread Id: 1
Starting ID 59. Thread Id: 1
Starting ID 60. Thread Id: 1
Starting ID 61. Thread Id: 1
Starting ID 62. Thread Id: 1
Starting ID 63. Thread Id: 1
Starting ID 64. Thread Id: 1
Starting ID 65. Thread Id: 1
Starting ID 66. Thread Id: 1
Starting ID 67. Thread Id: 1
Starting ID 68. Thread Id: 1
Starting ID 69. Thread Id: 1
Starting ID 70. Thread Id: 1
Starting ID 71. Thread Id: 1
Starting ID 72. Thread Id: 1
Starting ID 73. Thread Id: 1
Starting ID 74. Thread Id: 1
Starting ID 75. Thread Id: 1
Starting ID 76. Thread Id: 1
Starting ID 77. Thread Id: 1
Starting ID 78. Thread Id: 1
Starting ID 79. Thread Id: 1
Starting ID 80. Thread Id: 1
Starting ID 81. Thread Id: 1
Starting ID 82. Thread Id: 1
Starting ID 83. Thread Id: 1
Starting ID 84. Thread Id: 1
Starting ID 85. Thread Id: 1
Starting ID 86. Thread Id: 1
Starting ID 87. Thread Id: 1
Starting ID 88. Thread Id: 1
Starting ID 89. Thread Id: 1
Starting ID 90. Thread Id: 1
Starting ID 91. Thread Id: 1
Starting ID 92. Thread Id: 1
Starting ID 93. Thread Id: 1
Starting ID 94. Thread Id: 1
Starting ID 95. Thread Id: 1
Starting ID 96. Thread Id: 1
Starting ID 97. Thread Id: 1
Starting ID 98. Thread Id: 1
Starting ID 99. Thread Id: 1
Starting ID 100. Thread Id: 1
Completing ID 3. Response status code is OK. Thread Id: 14
Completing ID 10. Response status code is OK. Thread Id: 8
Completing ID 8. Response status code is OK. Thread Id: 9
Completing ID 7. Response status code is OK. Thread Id: 15
Completing ID 11. Response status code is OK. Thread Id: 14
Completing ID 4. Response status code is OK. Thread Id: 8
Completing ID 9. Response status code is OK. Thread Id: 9
Completing ID 12. Response status code is OK. Thread Id: 8
Completing ID 13. Response status code is OK. Thread Id: 8
Completing ID 6. Response status code is OK. Thread Id: 8
Completing ID 17. Response status code is OK. Thread Id: 8
Completing ID 18. Response status code is OK. Thread Id: 8
Completing ID 21. Response status code is OK. Thread Id: 8
Completing ID 24. Response status code is OK. Thread Id: 8
Completing ID 20. Response status code is OK. Thread Id: 8
Completing ID 30. Response status code is OK. Thread Id: 8
Completing ID 22. Response status code is OK. Thread Id: 8
Completing ID 34. Response status code is OK. Thread Id: 8
Completing ID 32. Response status code is OK. Thread Id: 9
Completing ID 33. Response status code is OK. Thread Id: 9
Completing ID 39. Response status code is OK. Thread Id: 9
Completing ID 35. Response status code is OK. Thread Id: 8
Completing ID 2. Response status code is OK. Thread Id: 9
Completing ID 44. Response status code is OK. Thread Id: 9
Completing ID 23. Response status code is OK. Thread Id: 8
Completing ID 31. Response status code is OK. Thread Id: 14
Completing ID 38. Response status code is OK. Thread Id: 14
Completing ID 43. Response status code is OK. Thread Id: 8
Completing ID 50. Response status code is OK. Thread Id: 9
Completing ID 1. Response status code is OK. Thread Id: 15
Completing ID 48. Response status code is OK. Thread Id: 14
Completing ID 27. Response status code is OK. Thread Id: 8
Completing ID 49. Response status code is OK. Thread Id: 9
Completing ID 28. Response status code is OK. Thread Id: 15
Completing ID 14. Response status code is OK. Thread Id: 14
Completing ID 29. Response status code is OK. Thread Id: 8
Completing ID 26. Response status code is OK. Thread Id: 14
Completing ID 15. Response status code is OK. Thread Id: 9
Completing ID 19. Response status code is OK. Thread Id: 8
Completing ID 25. Response status code is OK. Thread Id: 15
Completing ID 5. Response status code is OK. Thread Id: 14
Completing ID 40. Response status code is OK. Thread Id: 9
Completing ID 60. Response status code is OK. Thread Id: 8
Completing ID 37. Response status code is OK. Thread Id: 15
Completing ID 41. Response status code is OK. Thread Id: 14
Completing ID 16. Response status code is OK. Thread Id: 9
Completing ID 63. Response status code is OK. Thread Id: 14
Completing ID 36. Response status code is OK. Thread Id: 14
Completing ID 42. Response status code is OK. Thread Id: 9
Completing ID 45. Response status code is OK. Thread Id: 14
Completing ID 64. Response status code is OK. Thread Id: 14
Completing ID 53. Response status code is OK. Thread Id: 9
Completing ID 61. Response status code is OK. Thread Id: 15
Completing ID 52. Response status code is OK. Thread Id: 14
Completing ID 67. Response status code is OK. Thread Id: 14
Completing ID 74. Response status code is OK. Thread Id: 14
Completing ID 75. Response status code is OK. Thread Id: 14
Completing ID 62. Response status code is OK. Thread Id: 15
Completing ID 78. Response status code is OK. Thread Id: 8
Completing ID 66. Response status code is OK. Thread Id: 15
Completing ID 55. Response status code is OK. Thread Id: 14
Completing ID 83. Response status code is OK. Thread Id: 15
Completing ID 59. Response status code is OK. Thread Id: 14
Completing ID 68. Response status code is OK. Thread Id: 8
Completing ID 85. Response status code is OK. Thread Id: 15
Completing ID 47. Response status code is OK. Thread Id: 9
Completing ID 72. Response status code is OK. Thread Id: 14
Completing ID 65. Response status code is OK. Thread Id: 8
Completing ID 84. Response status code is OK. Thread Id: 8
Completing ID 70. Response status code is OK. Thread Id: 14
Completing ID 87. Response status code is OK. Thread Id: 14
Completing ID 56. Response status code is OK. Thread Id: 8
Completing ID 90. Response status code is OK. Thread Id: 15
Completing ID 76. Response status code is OK. Thread Id: 9
Completing ID 73. Response status code is OK. Thread Id: 14
Completing ID 69. Response status code is OK. Thread Id: 8
Completing ID 86. Response status code is OK. Thread Id: 15
Completing ID 81. Response status code is OK. Thread Id: 9
Completing ID 91. Response status code is OK. Thread Id: 15
Completing ID 77. Response status code is OK. Thread Id: 9
Completing ID 57. Response status code is OK. Thread Id: 15
Completing ID 98. Response status code is OK. Thread Id: 9
Completing ID 100. Response status code is OK. Thread Id: 15
Completing ID 79. Response status code is OK. Thread Id: 9
Completing ID 58. Response status code is OK. Thread Id: 15
Completing ID 80. Response status code is OK. Thread Id: 8
Completing ID 82. Response status code is OK. Thread Id: 14
Completing ID 89. Response status code is OK. Thread Id: 9
Completing ID 88. Response status code is OK. Thread Id: 15
Completing ID 97. Response status code is OK. Thread Id: 8
Completing ID 51. Response status code is OK. Thread Id: 14
Completing ID 94. Response status code is OK. Thread Id: 9
Completing ID 93. Response status code is OK. Thread Id: 15
Completing ID 71. Response status code is OK. Thread Id: 8
Completing ID 46. Response status code is OK. Thread Id: 14
Completing ID 54. Response status code is OK. Thread Id: 9
Completing ID 95. Response status code is OK. Thread Id: 15
Completing ID 92. Response status code is OK. Thread Id: 8
Completing ID 99. Response status code is OK. Thread Id: 14
Completing ID 96. Response status code is OK. Thread Id: 9
00:00:06.7737961

C:\Program Files\dotnet\dotnet.exe (process 296) exited with code 0.
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .

我认为23秒和7秒足以证明它是异步/等待方案。