【问题标题】:Inconsistencies with async/await/result using TPL in C#在 C# 中使用 TPL 与 async/await/result 不一致
【发布时间】:2017-05-23 01:43:21
【问题描述】:

我一直在用 C# 中的 TPL 进行一些测试,以更好地了解线程是如何创建的以及何时启动新线程。

在下面的测试中,第一个不使用async/await 的测试行为符合预期,并且总是返回我期望的值。但是,使用async/await 的最后 2 次测试非常不一致。

在我的测试中,我做了以下假设:

  1. 由于任务已完成,GetThreadIdInstant 将返回同一线程。
  2. GetThreadIdDelayed 将在不同的线程上返回,因为延迟不会立即返回。
  3. 由于使用了Task.Run()GetThreadIdForcedNew 将在不同的线程上返回。

有没有解释为什么上述假设在任务上使用.Result 时是正确的,但在使用async/await 时却不一致?

编辑:澄清“不一致”测试:所以最后 2 个测试使用 async/await,我仍然希望它们给出与使用 .Result 的第一个测试相同的结果,这是不正确的。但是,我在for 循环中包含代码的原因是某些迭代有效,然后我的Assert 语句将失败。我使用“不一致”这个词的原因是 b/c 不断地运行测试,并且在只运行它们与调试它们之间交替导致它们有时通过有时失败。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Parallels.Tests
{
    [TestClass]
    public class GetterTests
    {
        //this test always succeeds
        [TestMethod]
        public void ResultTest()
        {
            for (var i = 0; i < 500; i++)
            {
                var currentThreadId = Thread.CurrentThread.ManagedThreadId;

                var instantThreadId = ThreadGetter.GetThreadIdInstant().Result;
                var delayedThreadId = ThreadGetter.GetThreadIdDelayed().Result;
                var forcedNewThreadId = ThreadGetter.GetThreadIdForcedNew().Result;

                Assert.AreEqual(currentThreadId, instantThreadId);
                Assert.AreNotEqual(currentThreadId, delayedThreadId);
                Assert.AreNotEqual(currentThreadId, forcedNewThreadId);
            }
        }

        //mixed results
        [TestMethod]
        public async Task AwaitDelayedTest()
        {

            for (var i = 0; i < 500; i++)
            {
                try
                {
                    var currentThreadId = Thread.CurrentThread.ManagedThreadId;

                    var delayedThreadId = await ThreadGetter.GetThreadIdDelayed();

                    Assert.AreNotEqual(currentThreadId, delayedThreadId);
                }
                catch (Exception ex)
                {
                    throw new Exception($"failed at iteration: {i}", ex);
                }
            }

        }

        //mixed results
        [TestMethod]
        public async Task AwaitForcedNewTest()
        {
            for (var i = 0; i < 500; i++)
            {
                try
                {
                    var currentThreadId = Thread.CurrentThread.ManagedThreadId;

                    var forcedNewThreadId = await ThreadGetter.GetThreadIdForcedNew();

                    Assert.AreNotEqual(currentThreadId, forcedNewThreadId);
                }
                catch (Exception ex)
                {
                    throw new Exception($"failed at iteration: {i}", ex);
                }
            }
        }
    }

    public static class ThreadGetter
    {
        public static async Task<int> GetThreadIdInstant() => Thread.CurrentThread.ManagedThreadId;

        public static async Task<int> GetThreadIdDelayed()
        {
            await Task.Delay(1);
            return Thread.CurrentThread.ManagedThreadId;
        }

        public static async Task<int> GetThreadIdForcedNew() => await Task.Run(() => Thread.CurrentThread.ManagedThreadId);
    }
}

【问题讨论】:

  • 如果您能准确解释“非常不一致”的含义,那将非常有帮助。如果不知道结果是什么,就很难解释结果。
  • 另外我认为你的意思是TAP,而不是TPL。您的问题中似乎没有任何内容是特定于 TPL 的。
  • 我试图进一步澄清我所说的“不一致”的含义。

标签: c# multithreading asynchronous async-await task-parallel-library


【解决方案1】:

在此处查看this answer 的相关问题。

当您使用 .Result... 时,您正在阻塞当前线程,因此您的 GetThreadIdDelayed() 和 GetThreadIdForcedNew() 方法中的任务完成的工作总是 在线程以外的线程中运行调用线程。

当您使用 await... 虽然调用线程上不会继续执行,但该线程在子任务执行期间不会被消耗,并且可能(有时)用于完成某些子任务本身的工作。

另请注意,调用 await 后的执行并不总是默认返回同一线程。例如,在许多 UI 应用程序中,这一点尤其重要。您可以使用ConfigureAwait 控制此行为。

【讨论】:

    猜你喜欢
    • 2016-12-03
    • 2011-05-02
    • 1970-01-01
    • 2013-10-11
    • 2016-05-24
    • 1970-01-01
    • 2015-03-15
    • 1970-01-01
    • 2017-11-05
    相关资源
    最近更新 更多