Home > OS >  Func doesn't work & blocked during test execution
Func doesn't work & blocked during test execution

Time:05-09

Prerequisites:

  1. .Net 6.0
  2. C# 10
  3. NUnit 3.13.3

Context: Try to run an unit test, but run into some kind of thread blocker. The code just stop execution on

value = await getDataToCacheAsync.Invoke();

The last row that can be debugged is

return () => new Task<string?>(() => cacheValue [there]);

Q: It looks like there is some kind of deadlock happened, but it's not clear for me why and how it can be addressed

Unit test:

[Test]
public async Task GetCachedValueAsync_WithDedicatedCacheKey_ReturnsExpectedCacheValue()
{
    const string cacheKey = "test-cache-key";
    const string cacheValue = "test-cache-key";

    var result = await _sut.GetCachedValueAsync(cacheKey, GetDataToCacheAsync(cacheValue));

    Assert.AreEqual(cacheValue, result);
}

private static Func<Task<string?>> GetDataToCacheAsync(string cacheValue)
{
    return () => new Task<string?>(() => cacheValue);
}

The code under test:

public async Task<T?> GetCachedValueAsync<T>(string cacheKey, Func<Task<T?>> getDataToCacheAsync)
                where T : class
{

    // [Bloked here, nothing happens then, I'm expecting that it should return "test-cache-value"]

    value = await getDataToCacheAsync.Invoke(); [Blocked]
                ...
    return value
}

CodePudding user response:

After

return () => new Task<string?>(() => cacheValue [there]);

was replaces with

return () => Task.FromResult(cacheValue);

it started work

UPD:

It seems like the root cause is that a task should be started directly before awaiting it, in such cases (e.g. Task.Run(...), TaskFactory ... etc.).

Task.FromResult returns already completed task with a result

CodePudding user response:

As written in the docs generally you should try to avoid creating tasks via constructor:

This constructor should only be used in advanced scenarios where it is required that the creation and starting of the task is separated.

Rather than calling this constructor, the most common way to instantiate a Task object and launch a task is by calling the static Task.Run(Action) or TaskFactory.StartNew(Action) method.

The issue being that task created by constructor is a "cold" one (and according to guidelines you should avoid returning "cold" tasks from methods, only "hot" ones) - it is not started so await will result in endless wait (actually no deadlock happening here).

There are multiple ways to fix this code, for example:

  1. Use Task.Run:
return () => Task.Run(() => cacheValue);
  1. Start created task manually (though in this case there is no reason to):
return () => 
{ 
    var task = new Task<string?>(() => cacheValue); 
    task.Start(); 
    return task;
}
  1. Return a completed task (which I think is the best way in this case, unless you are testing a very specific scenario):
return () => Task.FromResult(cacheValue);
  • Related