question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Deadlock from calling Task.Result

See original GitHub issue

Example Code

Here is a test that exhibits a deadlock.

[Fact]
public void MTaskFold_InAsyncContextWithTaskWaitingForActivation_Halts() =>
  AsyncContext.Run(() => MTaskFold_WithTaskWaitingForActivation_Halts());

private static void MTaskFold_WithTaskWaitingForActivation_Halts() {
  var intTask = TimeSpan
    .FromMilliseconds(100)
    .Apply(Task.Delay)
    .ContinueWith(_ => 0);

  var actual = default(MTask<int>)
    .Fold(intTask, 0, (x, y) => 0)
    (Unit.Default);

  // execution terminates by reaching here
}

The type AsyncContext is from the NuGet package Nito.AsyncEx.

Expected Behavior

As stated in the test, the expected behavior is that the test terminates.

Actual Behavior

The test does not terminate. Here is the source code under test.

[Pure]
public Func<Unit, S> Fold<S>(Task<A> ma, S state, Func<S, A, S> f) => _ =>
{
  if (ma.IsFaulted) return state;
  return f(state, ma.Result);
};

The test does not terminate because of a deadlock that is caused when calling ma.Result.

Possible Fix

Stephen Cleary, an expert on this topic and author of the aforementioned NuGet package Nito.AsyncEx, explains why this deadlock occurs. To summarize, my understanding is that it is a best practice to not call Task.Result unless Task.Status == TaskStatus.RanToCompletion. This is not true in the example that I have provided. Instead it is in the state TaskStatus.WaitingForActivation.

Otherwise, the best practice is to await the task after calling Task.ConfigureAwait(false). I don’t see how you can do this without changing the return type of Fold. If I may adjust the return type slightly from Func<Unit, S> to Func<Unit, Task<S>>, then I would implement Stephen’s best practice suggestion like this.

[Pure]
public Func<Unit, Task<S>> Fold<S>(Task<A> ma, S state, Func<S, A, S> f) => async _ =>
{
    if (ma.IsFaulted) return state;
    return f(state, await ma.ConfigureAwait(false));
};

This new return type seems more natural to me. My understanding of monads is that you are not allowed to just extract the underlying value of the monad. Unfortunately, Fold is doing just that when it calls Task.Result. However, I have only recently started using monads and your library, so I know that I still have a great deal to learn and that it is very possible that I am misunderstanding something here.

Thank you for considering my bug report. I am interested to hear your thoughts about it.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:2
  • Comments:18 (18 by maintainers)

github_iconTop GitHub Comments

2reactions
louthycommented, Dec 20, 2017

You’re both right for different reasons. Yes, the quick fix would be to asyncify the code. But what I’ve actually started doing is looking into breaking Monad apart into Monad and MonadAsync. The attempt to unify the two has lead to a slightly messy interface and compromises like this implementation of Fold for MTask; it should be FoldAsync that returns a Task.

I have a busy few days coming up, but hopefully I should have something by the end of the week.

1reaction
faeriedustcommented, Dec 20, 2017

Yeah, I tried to fix the bug in the repo and immediately ran into many issues…

I think breaking Monad into Monad and MonadAsync does seem likely the much better approach.

I’m looking forward to this. 😃

Read more comments on GitHub >

github_iconTop Results From Across the Web

'await' works, but calling task.Result hangs/deadlocks
"The Result property is a blocking property. If you try to access it before its task is finished, the thread that's currently active...
Read more >
Understanding Async, Avoiding Deadlocks in C# | by Eke ...
If you are calling from UI thread, you will deadlock instantly, as the task is queued for the UI thread which gets blocked...
Read more >
Why can't Task<T>.Result be modified to not deadlock?
The reason it deadlocks is you are "asking" the compiler to create a deadlock by using the .Result method. This issued has existed...
Read more >
Task.Result and Task.Wait design flaw leads to deadlock in ...
This example proves that absolutely any call to Task.Result or Task.Wait (before task completion) from ThreadPool thread will work for some time ...
Read more >
C# Deadlocks in Depth – Part 2
But, the task is waiting for the UI Thread to be freed in order to finish the Dispatcher.Invoke() call. Hence, we have a...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found