简体   繁体   中英

Inconsistencies with async/await/result using TPL in C#

I've been doing some testing with the TPL in C# to better understand how threads are created and when exactly new threads are kicked off.

With the below tests, The first one, using no async/await behaves as expected and always returns the values I would expect. However, the final 2 tests using async/await are very inconsistent.

In my tests, I made the following assumptions:

  1. GetThreadIdInstant would return on the same thread due to the task being completetd.
  2. GetThreadIdDelayed would return on a different thread due to the delay not returning instantly.
  3. GetThreadIdForcedNew would return on a different thread due to the use of Task.Run() .

Is there an explanation why the above assumptions are true when using .Result on a task, but not consistently true when using async/await ?

Edit: Clarification on "inconsistent" tests: So the last 2 tests using async/await , I still expected them to give the same results as the first test using .Result , which is not true. However, the reason I have the code inside of a for loop is that some iterations work, and then later on my Assert statement will fail. The reason I used the word "inconsistent" is b/c continuously running the tests, and alternating between just running them versus debugging them causes them to sometimes pass and sometimes fail.

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);
    }
}

See this answer to a related question here.

When you use .Result... you are blocking the current thread, and thus work done on the tasks in your GetThreadIdDelayed() and GetThreadIdForcedNew() methods always runs in a thread other than the calling thread.

When you use await... Although execution does not continue on the calling thread, that thread is not consumed during the execution of the subtasks, and may (sometimes) be used to do the work of some of the subtasks themselves.

Also note that execution after a call to await does not always return on the same thread by default. This is especially important, for example, in many UI applications. You can control this behavior using ConfigureAwait .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM