2

The design of the upcoming C# 8 IAsyncEnumerable uses ValueTask and ValueTask<T> to communicate the potentially synchronous results back to the consumer logic. They both have the IsFaulted property, but unlike Task, there is no Exception property.

Is it even possible to have a ValueTask that doesn't hold a normal Task and is in the faulted or canceled state?

The ValueTask<T>.Result's documentation indicates calling it on a failed task will rethrow the contained Exception. Would the following code make thus sense to extract the Exception?

IAsyncEnumerable<int> asyncSequence = ...

ValueTask<bool> valueTask = asyncSequence.MoveNextAsync();

if (valueTask.IsFaulted) {
    // this has to work in a non-async method
    // there is no reason to block here or use the
    // async keyword
    try {
        var x = valueTask.Result;
    } catch (Exception ex) {
        // work with the exception
    }
}

ValueTask endTask = asyncSequence.DisposeAsync();

if (endTask.IsFaulted) {
    // there is no ValueTask.Result property
    // so the appoach above can't work
}

The non-generic ValueTask does not have a Result property. How can I extract the Exception then?

Generally, I suppose applying AsTask could be used for both extractions, however, I'd think it incurs allocation making the use of ValueTask questionable in the first place as I understand it.

akarnokd
  • 69,132
  • 14
  • 157
  • 192
  • Is there a reason your using `.Result` and not `async`/`await`? You do know that (depending on your context) this [can deadlock](https://stackoverflow.com/questions/17248680/await-works-but-calling-task-result-hangs-deadlocks)? – Liam Oct 22 '18 at 13:32
  • Possible duplicate of [How to call asynchronous method from synchronous method in C#?](https://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c) – Liam Oct 22 '18 at 13:35
  • The above is based on your comment `this has to work in a non-async method` This seems to be your underlying issue but it's hard to tell without more context – Liam Oct 22 '18 at 13:35
  • It's not a duplicate as I have issues with `ValueTask`. The linked question doesn't even mention `ValueTask`. I've updated the question with a more detailed example code. – akarnokd Oct 22 '18 at 13:43
  • `ValueTask` should work the same as `Task`. The correct solution is to use `async` her, if not then you should `Task.Run(async () => {});` unless your really sure your not going to get deadlock issues. [Any sync call to an async method can deadlock if you don't do this. `ValueTask` or not](https://stackoverflow.com/a/52425150/542251) – Liam Oct 22 '18 at 13:47
  • So, yes, this is mostly covered by that duplicate. – Liam Oct 22 '18 at 13:48
  • "Should work the same": probably if you consider working with it via `await` but I can't use `await`. The context, in case it gives more insight, is that I have to work with those `IAsyncEnumerable`s and their `ValueTask`s from a non-async capable environment such as Rx/Reactive Streams where methods are defined non-async hence I can't use "await". – akarnokd Oct 22 '18 at 13:54
  • 1
    There's no way to retrieve the exception from an `IValueTaskSource`, so from there I see two ways: either `.AsTask().Exception` or `.GetAwaiter().GetResult()` and catch the exception. Surprisingly, `AsTask()` actually make the underlying `IValueTaskSource` throw the exception, catches it, and creates a faulted task from it: https://source.dot.net/#System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs,172 At first I was puzzled that there isn't a more optimized path, but in the end exceptions are so costly that once you get one you shouldn't care about micro-optimizations anymore – Kevin Gosse Oct 23 '18 at 06:59
  • And I took a shortcut, sorry. I was reacting on `Is it even possible to have a ValueTask that doesn't hold a normal Task and is in the faulted or canceled state`: the answer is yes, if the code that generated the task used `IValueTaskSource`. Then the task will be lazily generated on demand – Kevin Gosse Oct 23 '18 at 07:01
  • Thanks @KevinGosse. Given that `IsFaulted` exists, I'd think `Exception` could also exist and delegate similarly. Maybe I should try and make an enhancement request to the corefx? – akarnokd Oct 23 '18 at 09:29
  • @akarnokd Since IValueTaskSource is something they built for performance I suppose they already considered all that, but there's no harm in asking – Kevin Gosse Oct 23 '18 at 09:48
  • 1
    Posted it: https://github.com/dotnet/corefx/issues/32977 – akarnokd Oct 23 '18 at 10:11

3 Answers3

1

The common way to block until completion of a Task or Task<T> is by invoking the Task.Wait() method.

If an exception is thrown, it will be a System.AggregatedException. The InnerExceptions property will have the collection of exceptions that caused the current exception.

Starting with .NET Framework 4.5 and .NET Core 1.0 there's also the GetAwaiter() method where you can invoke GetResult() on it's return value and will throw the first exception in the collection.

But you should never do that!!!

You should do this:

// this has to work in a non-async method
try {
    var x = await valueTask;
} catch (Exception ex) {
    // work with the exception
}

ValueTask and ValueTask<T> are used to avoid heap allocations when, potentially, the result is already available. It doesn't mean that it's safe to block on that task.

Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59
  • First, I have to work with `ValueTask` where `IsFaulted` is `true` thus I don't have to block for anything. Second [GetAwaiter](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask-1.getawaiter?view=netcore-2.1#System_Threading_Tasks_ValueTask_1_GetAwaiter) seems to allocate, thus I wouldn't be likely better than using `AsTask`. Third, the extraction should work without `await` as the parent method is not and can't be marked `async`. – akarnokd Oct 22 '18 at 13:34
  • If you can't use `async` stick with `IEnumerable`. – Paulo Morgado Oct 22 '18 at 19:57
  • Maybe I wasn't clear but working with `ValueTask` is mandatory. I can't change to `IEnumerable` because it makes no semantic sense for my case and not possible as the API I'm trying to interact with is not defined by me. – akarnokd Oct 22 '18 at 20:18
  • Where are you seeing [GetAwaiter](https://source.dot.net/#System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs,e1e6145b38914784) allocating? – Paulo Morgado Oct 22 '18 at 20:51
  • 1
    You shouldn't be mixing synchronous and asynchronous APIs. Don't be surprised when it deadlocks. – Paulo Morgado Oct 22 '18 at 20:51
  • [`GetAwaiter().OnCompleted()`](https://source.dot.net/#System.Private.CoreLib/shared/System/Runtime/CompilerServices/ValueTaskAwaiter.cs,5d7634e3d02b9ce0) requires a delegate for which context has to be added, thus it needs allocation. Plus, I still can't get the Exception through that. "shouldn't be mixing" - I'm quite experienced with sync/async code and deadlock is not a concern here. Getting that Exception out is. – akarnokd Oct 22 '18 at 21:39
1

The response by the corefx team indicates this is indeed not possible directly and can't be easily added due to the need to change a companion interface and that would bring complications along.

The workaround suggested, as I guessed originally, to use AsTask to get the Exception.

IAsyncEnumerable<int> asyncSequence = ...

ValueTask<bool> valueTask = asyncSequence.MoveNextAsync();

if (valueTask.IsFaulted) {
    var ex = valueTask.AsTask().Exception;
    // work with the exception
}

ValueTask endTask = asyncSequence.DisposeAsync();

if (endTask.IsFaulted) {
    var ex = valueTask.AsTask().Exception;
    // work with the exception
}
akarnokd
  • 69,132
  • 14
  • 157
  • 192
-2

Your best bet would be to use a try statement and attempt to catch the exception there and then handle it later... In regards to the ValueTask that doesn't hold a normal task, I would think that you'd be able to use lambda methods to resolve that.

try {
    var x = Task.Run(()=>{ ... });
}
catch (Exception ex) {
    // Do something with exception.
}

Or whatever the case would be for ValueTask. Considering C# has focused on Async tasks a lot these past builds, it would be safe to say that you would use lambda and event calls.

private void foo(ValueTask task) {
        lock (threadObj) {
            var x = Task.Run(()=> { return task.doWork(); }
            if (x == true) {
                // then do other work...
            }
            else {
                // handle 'exception'...
            }
        }
}
Linas Paz
  • 11
  • 4
  • This answer makes no sense to me. The starting point is that I have a `ValueTask` returned to me by some 3rd party method. Also what "lambdas" and "event calls" are you referring in respect to `ValueTask`? – akarnokd Oct 22 '18 at 11:35
  • Sorry I didn't make much sense. But in essence lambdas are methods encased in parenthesis and brackets. ie foo(object sender, EventArgs e) equates to (s,e)=>{...} and event calls are utilizing events and invoking them. ie someEvent?.Invoke((s,e)=>{...}) This example uses lambads and an event call to invoke a method as a parameter... This kind of programming is typically used in System.Linq. Therefore, what I'm saying is that as soon as you have that valueTask utilize it in a thread-safe context, which can allow you to catch unwanted actions expressed by the program. – Linas Paz Oct 22 '18 at 12:35
  • My question has nothing to do with lambdas or creating tasks. I already have a `ValueTask` from somewhere out of my control and have to get the `Exception` out of it. – akarnokd Oct 22 '18 at 13:21