Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: GetAwaiter for tuple of tasks #20166

Closed
jcouv opened this issue Feb 9, 2017 · 64 comments
Closed

Proposal: GetAwaiter for tuple of tasks #20166

jcouv opened this issue Feb 9, 2017 · 64 comments
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Threading.Tasks
Milestone

Comments

@jcouv
Copy link
Member

jcouv commented Feb 9, 2017

@jnm2 has implemented GetAwaiter extension methods which enable convenient awaiting on tuples of tasks (up to some arity).

For instance, var (x, y) = await (thingX.OperationAsync(), thingY.OperationAsync());

@terrajobst @weshaggard @jkotas How do you recommend proceeding with such API proposals?
Do you need an API review before going into implementation?
If a PR is ok, I'm assuming that the Task-related extensions would go into https://github.com/dotnet/corefx/tree/master/src/Common/src/System/Threading/Tasks Is that appropriate?

Also, more generally, do you prefer to review such proposal piecemeal (one at a time) or all together (to get a well-thought API surface?). I remember some proposals for Zip and Join in LINQ APIs.

Relates to dotnet/roslyn#16159

Proposal

There are three parts which can be considered separately.

1. Simple awaiting

Example:

var (foo, bar) = await (GetFooAsync(), GetBarAsync());

Proposed API:

namespace System.Threading.Tasks
{
    public static class TaskTupleExtensions
    {
        public static TaskTupleExtensions.TupleTaskAwaiter<T1, T2> GetAwaiter<T1, T2>(this (Task<T1>, Task<T2>) tasks);

        public struct TupleTaskAwaiter<T1, T2> : System.Runtime.CompilerServices.ICriticalNotifyCompletion
        {
            public bool IsCompleted { get; }

            public TupleTaskAwaiter((Task<T1>, Task<T2>) tasks);

            public (T1, T2) GetResult();

            public void OnCompleted(System.Action continuation);

            public void UnsafeOnCompleted(System.Action continuation);
        }
    }
}

2. Configured awaiting

Example:

var (foo, bar) = await (GetFooAsync(), GetBarAsync()).ConfigureAwait(false);

Proposed API:

namespace System.Threading.Tasks
{
    public static class TaskTupleExtensions
    {
        public static TaskTupleExtensions.TupleConfiguredTaskAwaitable<T1, T2> ConfigureAwait<T1, T2>(this (Task<T1>, Task<T2>) tasks, bool continueOnCapturedContext);

        public struct TupleConfiguredTaskAwaitable<T1, T2>
        {
            public TupleConfiguredTaskAwaitable((Task<T1>, Task<T2>) tasks, bool continueOnCapturedContext);

            public TaskTupleExtensions.TupleConfiguredTaskAwaitable<T1, T2>.Awaiter GetAwaiter();

            public struct Awaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion
            {
                public bool IsCompleted { get; }

                public Awaiter((Task<T1>, Task<T2>) tasks, bool continueOnCapturedContext);

                public (T1, T2) GetResult();

                public void OnCompleted(System.Action continuation);

                public void UnsafeOnCompleted(System.Action continuation);
            }
        }
    }
}

3. Void-returning awaiting

Example:

await (DoFooAsync(), DoBarAsync());

await (DoFooAsync(), DoBarAsync()).ConfigureAwait(false);

Proposed API:

namespace System.Threading.Tasks
{
    public static class TaskTupleExtensions
    {
        public static System.Runtime.CompilerServices.TaskAwaiter GetAwaiter(this (Task, Task) tasks);

        public static System.Runtime.CompilerServices.ConfiguredTaskAwaitable ConfigureAwait(this (Task, Task) tasks, bool continueOnCapturedContext);
    }
}

Higher arities

The above can be trivially generalized to any higher arity. Ten is tentatively suggested. I don’t recall seeing arities greater than four in practice. Eight would be weird because the return type would be ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>>.

If you actually need to parallelize five async operations, there’s nothing that performs better and is easier to write than the following:

Example:

var (
    dependency1,
    dependency2,
    dependency3,
    dependency4,
    dependency5
) = await (
    GetDependency1Async(),
    GetDependency2Async(),
    GetDependency3Async(),
    GetDependency4Async(),
    GetDependency5Async()
).ConfigureAwait(false);

Degenerate arity

The unary case could probably be left out for now since C# has no syntax for 1-tuples, but here it is for completeness:

Example:

var foo = await ValueTuple.Create(GetFooAsync());

var bar = await ValueTuple.Create(GeBarAsync()).ConfigureAwait(false);

await ValueTuple.Create(DoFooAsync());

await ValueTuple.Create(DoBarAsync()).ConfigureAwait(false);

Proposed API:

namespace System.Threading.Tasks
{
    public static class TaskTupleExtensions
    {
        public static System.Runtime.CompilerServices.TaskAwaiter GetAwaiter(this System.ValueTuple<Task> tasks);

        public static System.Runtime.CompilerServices.TaskAwaiter<T1> GetAwaiter<T1>(this System.ValueTuple<Task<T1>> tasks);

        public static System.Runtime.CompilerServices.ConfiguredTaskAwaitable ConfigureAwait(this System.ValueTuple<Task> tasks, bool continueOnCapturedContext);

        public static System.Runtime.CompilerServices.ConfiguredTaskAwaitable<T1> ConfigureAwait<T1>(this System.ValueTuple<Task<T1>> tasks, bool continueOnCapturedContext);
    }
}

Questions

Should TaskTupleExtensions.TupleTaskAwaiter perhaps be renamed TaskTupleExtensions.TaskTupleAwaiter?

Entire API set

https://gist.github.com/jnm2/47a6a014831615bdb7b64a75f09a5799

@jcouv jcouv changed the title GetAwaiter for tuple of tasks Proposal: GetAwaiter for tuple of tasks Feb 9, 2017
@jcouv jcouv self-assigned this Feb 9, 2017
@alexperovich
Copy link
Member

alexperovich commented Feb 9, 2017

We would want a formal API proposal before implementation. I think this is a useful api to have. The right place for them would be in https://github.com/dotnet/corefx/tree/master/src/System.Threading.Tasks/src/System/Threading/Tasks.

@jcouv
Copy link
Member Author

jcouv commented Feb 9, 2017

@alexperovich Thanks. Would you have a past example of formal API proposal that we could use as template?

@HaloFour
Copy link

HaloFour commented Feb 9, 2017

Awaiting multiple asynchronous operations is a very common pattern in my project. It's a public-facing WebAPI portal to a large number of microservices where it's normal for a single incoming request to require making multiple backend HTTP requests to aggregate the data. We currently use some helper extension methods to return a System.Tuple<...> of the results. Having BCL support which would enable await on a tuple of tasks which can be immediately deconstructed into its results would make that pattern significantly easier.

@alexperovich
Copy link
Member

@jcouv see #15725

@jkotas
Copy link
Member

jkotas commented Feb 9, 2017

@alexperovich alexperovich self-assigned this Feb 9, 2017
@jcouv jcouv removed their assignment Feb 10, 2017
@alexperovich alexperovich removed their assignment Feb 16, 2017
@jcouv jcouv self-assigned this Apr 20, 2017
@jnm2
Copy link
Contributor

jnm2 commented Aug 25, 2017

Fyi, added ConfigureAwait support to TaskTupleExtensions.

@Porges
Copy link

Porges commented Dec 11, 2017

What is needed to push along this proposal – a more formal write-up?

@jnm2
Copy link
Contributor

jnm2 commented Mar 6, 2018

@karelz Why has this stalled? Looks like we're waiting for step 2.

@karelz
Copy link
Member

karelz commented Mar 6, 2018

@jnm2 the "assigned person" already replied a year ago: https://github.com/dotnet/corefx/issues/16010#issuecomment-278776513
Next step is to see formal API proposal as mentioned above.

@jnm2
Copy link
Contributor

jnm2 commented Mar 7, 2018

Proposal

There are three parts which can be considered separately.

1. Simple awaiting

Example:

var (foo, bar) = await (GetFooAsync(), GetBarAsync());

Proposed API:

namespace System.Threading.Tasks
{
    public static class TaskTupleExtensions
    {
        public static TaskTupleExtensions.TupleTaskAwaiter<T1, T2> GetAwaiter<T1, T2>(this (Task<T1>, Task<T2>) tasks);

        public struct TupleTaskAwaiter<T1, T2> : System.Runtime.CompilerServices.ICriticalNotifyCompletion
        {
            public bool IsCompleted { get; }

            public TupleTaskAwaiter((Task<T1>, Task<T2>) tasks);

            public (T1, T2) GetResult();

            public void OnCompleted(System.Action continuation);

            [System.Security.SecurityCritical]
            public void UnsafeOnCompleted(System.Action continuation);
        }
    }
}

2. Configured awaiting

Example:

var (foo, bar) = await (GetFooAsync(), GetBarAsync()).ConfigureAwait(false);

Proposed API:

namespace System.Threading.Tasks
{
    public static class TaskTupleExtensions
    {
        public static TaskTupleExtensions.TupleConfiguredTaskAwaitable<T1, T2> ConfigureAwait<T1, T2>(this (Task<T1>, Task<T2>) tasks, bool continueOnCapturedContext);

        public struct TupleConfiguredTaskAwaitable<T1, T2>
        {
            public TupleConfiguredTaskAwaitable((Task<T1>, Task<T2>) tasks, bool continueOnCapturedContext);

            public TaskTupleExtensions.TupleConfiguredTaskAwaitable<T1, T2>.Awaiter GetAwaiter();

            public struct Awaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion
            {
                public bool IsCompleted { get; }

                public Awaiter((Task<T1>, Task<T2>) tasks, bool continueOnCapturedContext);

                public (T1, T2) GetResult();

                public void OnCompleted(System.Action continuation);

                [System.Security.SecurityCritical]
                public void UnsafeOnCompleted(System.Action continuation);
            }
        }
    }
}

3. Void-returning awaiting

Example:

await (DoFooAsync(), DoBarAsync());

await (DoFooAsync(), DoBarAsync()).ConfigureAwait(false);

Proposed API:

namespace System.Threading.Tasks
{
    public static class TaskTupleExtensions
    {
        public static System.Runtime.CompilerServices.TaskAwaiter GetAwaiter(this (Task, Task) tasks);

        public static System.Runtime.CompilerServices.ConfiguredTaskAwaitable ConfigureAwait(this (Task, Task) tasks, bool continueOnCapturedContext);
    }
}

Higher arities

The above can be trivially generalized to any higher arity. Ten is tentatively suggested. I don’t recall seeing arities greater than four in practice. Eight would be weird because the return type would be ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>>.

If you actually need to parallelize five async operations, there’s nothing that performs better and is easier to write than the following:

Example:

var (
    dependency1,
    dependency2,
    dependency3,
    dependency4,
    dependency5
) = await (
    GetDependency1Async(),
    GetDependency2Async(),
    GetDependency3Async(),
    GetDependency4Async(),
    GetDependency5Async()
).ConfigureAwait(false);

Degenerate arity

The unary case could probably be left out for now since C# has no syntax for 1-tuples, but here it is for completeness:

Example:

var foo = await ValueTuple.Create(GetFooAsync());

var bar = await ValueTuple.Create(GeBarAsync()).ConfigureAwait(false);

await ValueTuple.Create(DoFooAsync());

await ValueTuple.Create(DoBarAsync()).ConfigureAwait(false);

Proposed API:

namespace System.Threading.Tasks
{
    public static class TaskTupleExtensions
    {
        public static System.Runtime.CompilerServices.TaskAwaiter GetAwaiter(this System.ValueTuple<Task> tasks);

        public static System.Runtime.CompilerServices.TaskAwaiter<T1> GetAwaiter<T1>(this System.ValueTuple<Task<T1>> tasks);

        public static System.Runtime.CompilerServices.ConfiguredTaskAwaitable ConfigureAwait(this System.ValueTuple<Task> tasks, bool continueOnCapturedContext);

        public static System.Runtime.CompilerServices.ConfiguredTaskAwaitable<T1> ConfigureAwait<T1>(this System.ValueTuple<Task<T1>> tasks, bool continueOnCapturedContext);
    }
}

Questions

Should TaskTupleExtensions.TupleTaskAwaiter perhaps be renamed TaskTupleExtensions.TaskTupleAwaiter?

Entire API set

https://gist.github.com/jnm2/47a6a014831615bdb7b64a75f09a5799

@alexperovich, is there anything else you need?

/cc @buvinghausen, fyi

@jnm2
Copy link
Contributor

jnm2 commented Mar 7, 2018

@karelz Thanks for clearing that up. Is there anything I've missed?

@karelz
Copy link
Member

karelz commented Mar 7, 2018

@kouvel will do first-pass of the API review as area expert & owner.

@kouvel
Copy link
Member

kouvel commented Mar 15, 2018

@karelz I realize I'm supposed to own this area but I am no expert in this area. The proposal looks fine to me, CC @stephentoub

@Thaina
Copy link

Thaina commented Apr 11, 2018

ufcpp has suggest this

public static System.Runtime.CompilerServices.TaskAwaiter<(T1, T2)> GetAwaiter<T1, T2>(this (Task<T1>, Task<T2>) tasks)
{
    return Task.Run(async() => (await tasks.Item1,await tasks.Item2)).GetAwaiter();
}

Work like charmed

public static async Task Test()
{
    var (x,y)	= await (Task.Run(() => 0),Task.Run(() => 0));
}

Just need to put it in BCL for tuple

@Thaina
Copy link

Thaina commented Apr 11, 2018

But then again would it possible to have a mixed mode?

I mean

var (x,y)	= await (0,TaskInt); // became (int,int);

Which then we need permutation of all task/nontask tuple combination?

@jnm2
Copy link
Contributor

jnm2 commented Apr 11, 2018

@Thaina Yes, this issue is about moving the suggested methods into the BCL.

I don't see a benefit to the so-called mixed mode. The syntax would be contradictory to what is happening since you aren't awaiting both things, and it would require an exponential number of overloads per arity rather than a single overload per parity. It's rare but not unheard-of to await a Task<Task<T>>. Let's not blur the distinction between T and Task<T>.

@Thaina
Copy link

Thaina commented Apr 24, 2018

@jnm2 That's the reason I was propose dotnet/corefx#1454 and prefer dotnet/csharplang#1454 (comment) than this issue

@jnm2
Copy link
Contributor

jnm2 commented Apr 24, 2018

@Thaina dotnet/csharplang#1454 has one big issue with it that I'm not sure I like: if you use shapes, there's no Task.WhenAll to use because you aren't only operating on Task instances. There's going to be much more overhead involved than with this issue. Also, these two issues are certainly not mutually exclusive. 😃

@Thaina
Copy link

Thaina commented Apr 24, 2018

@jnm2 I don't think that would be a problem. Because we don't really need WhenAll for tuple. Each tasks would already be started and would likely work in parallel at the time it was put in the Tuple. The await in GetAwaiter just ensure each of them would ended

Or is it another reason you would need WhenAll in this case?

@HaloFour
Copy link

@Thaina

Task.WhenAll is what joins the results of all of those individual Task instances back into a single Task that can be awaited. Without that you'd have to implement that logic yourself by enumeration all of the awaitables and registering for completion individually and counting down. It's not hard, but it's not trivial bookkeeping.

@jnm2
Copy link
Contributor

jnm2 commented Apr 24, 2018

@Thaina Let's move the discussion to the csharplang issue, since this one is about BCL methods for C# 7.

@sharwell
Copy link
Member

sharwell commented Apr 24, 2018

If this was a discussion about ValueTuple<Task, Task> and the like, I think it would make more sense. However, since this proposal is inseparable from the tuple syntax in C#, I would encourage revisiting this from the language perspective, similar to how == and != were implemented.

I would expect a tuple of awaitables to be itself awaitable. Since this is pattern-based language feature, I don't see a way to add it as a library without a combinatorial explosion. The standalone library appears to be a fine placeholder in the absence of language support.

@sharwell
Copy link
Member

I discussed this with @jnm2 separately. It sounds like the implementation of the proposed method behaves the way I would expect for Task types:

  1. The join operation is performed with the semantics of Task.WhenAll
  2. In the event of one or more exceptions/cancellations, the first resulting exception is the one thrown (matching other await operations that throw the first inner exception)

However, given the ability to implement the requested functionality in a separate library with apparently no negative impact on performance or usability, I don't see a pressing concern that would need to move this issue forward prior to reaching an agreement for if/how the language would want to implement a similar feature within the compiler.

The most important concern for me is ensuring await operations on a tuple behave the same way whether they are handled by the compiler or handled by the class library. Deferring this proposal until a consensus is reached on the language aspects provides the greatest flexibility to ensure this condition holds.

@jcouv
Copy link
Member Author

jcouv commented Oct 8, 2018

I'd be fine with that.
@jnm2 What do you think?

@Porges
Copy link

Porges commented Oct 8, 2018

A nuget package already exists (TaskTupleAwaiter). I'm using it in practically every project I have that does async operations, as it's the most straightforward way to introduce concurrency.

@tarekgh
Copy link
Member

tarekgh commented Oct 8, 2018

Interesting, looking at nuget I am seeing the following:

2,180 total downloads
397 downloads of latest version
5 downloads per day (avg)

which looks there is some demand on that.

@stephentoub considering the demand, I think this will be better to be in the core. if you don't have a strong feeling against, I'll proceed marking this ready for review.

@jnm2
Copy link
Contributor

jnm2 commented Oct 8, 2018

That's pretty good for a NuGet package that isn't advertised. I haven't used it myself because I copy my gist into so many projects, but it isn't a bad option.

It's both a cool concept and useful, so I'd still favor having it be available by default. Everyone feels this way about their favorite API, so take that for what it's worth. However, I do see people get concurrent asynchony wrong: failing to observe exceptions in a second task if awaiting the first fails or cancels, or using Task.Result because the WhenAll result type is unwieldy.

There would be benefits to having a built-in and convenient syntax for doing this correctly. I wouldn't have to lead with, "So if you install this NuGet package... the correct syntax isn't terrible."

@stephentoub
Copy link
Member

stephentoub commented Oct 9, 2018

I think this will be better to be in the core. if you don't have a strong feeling against, I'll proceed marking this ready for review.

If you do, please ensure it's not part of the shared framework. This isn't worth increasing the size of every self-contained app by ~27K (which is the size of that .dll), or even the shared framework by a similar amount.

@jnm2
Copy link
Contributor

jnm2 commented Oct 9, 2018

Won't tree shaking advance to the point where it can remove this kind of concern?

@stephentoub
Copy link
Member

stephentoub commented Oct 9, 2018

I don't want to bet on it for things like this. If someone wants it, they can reference it, ship it with their app, etc.

@jnm2
Copy link
Contributor

jnm2 commented Oct 9, 2018

I wonder how much of that 27K will melt away when sharing headers, a string heap, AssemblyRef, TypeRef, and MemberRef tables, etc? 1.5K is strings, for instance, and the *Ref tables are busy.

@stephentoub
Copy link
Member

stephentoub commented Oct 9, 2018

My point is, it's a ton of stuff. This is functionality that, IMHO, will benefit very few people (who may love it), nothing in core is going to depend on it, it's completely external to anything in the rest of the implementation and can easily be built on top, and every .NET customer shouldn't have to pay anything for it unless they want to use it.

I do not understand why we're afraid of NuGet packages. Everything can't ship in the box, nor should it. We should be embracing the ecosystem, not hampering it by implying that things are only useful if they're shipped in the framework.

@jnm2
Copy link
Contributor

jnm2 commented Oct 9, 2018

I agree with not shipping everything inbox, yes. I'm not convinced that it will benefit very few people. If you use WhenAll today with non-void results, you would almost certainly prefer it:

var (policy, preferences) = await (
    GetPolicyAsync(policyId, cancellationToken),
    GetPreferencesAsync(cancellationToken)
).ConfigureAwait(false);

Today's option:

// WhenAll requires all tasks to have the same result type,
// so Task.WhenAll(intTask, stringTask) will not compile.

var policyTask = GetPolicyAsync(policyId, cancellationToken);
var preferencesTask = GetPreferencesAsync(cancellationToken);

await Task.WhenAll((Task)policyTask, (Task)preferencesTask).ConfigureAwait(false);

var policy = policyTask.Result; // Exceptions are already thrown by the await, if any
var preferences = preferencesTask.Result;

Or if the tasks happen to have the same result type:

var tempName = await (
    GetPolicyAsync(policyId, cancellationToken),
    GetPreferencesAsync(cancellationToken)
).ConfigureAwait(false);

var policy = tempName[0];
var preferences = tempName[1];

This is what I think merits preference over Task.WhenAll.

@jnm2
Copy link
Contributor

jnm2 commented Oct 9, 2018

Suppose we stop at arity 3 instead of 10, and skip the void-resulting versions which hardly save you much?

@Thaina
Copy link

Thaina commented Oct 9, 2018

@stephentoub nuget can't be used in unity. I have frustrate so long that tuple cannot be used in unity. Until unity 2018 that contains C# 7. But nuget really cannot be used everywhere

Everything that highly generic and could be used by many fields should be in the core as soon as possible

If System.Math and System.Threading.Tasks.Task is in nuget I would consider that everyone should use nuget. But because various reason. I think nuget is the same as custom dll. nuget just easier than manualling copy

And so if you have custom dll that you would always copy to more than half of your project. And many people make their own version on similar feature. It should always be in the core. And might be having native syntax support on that things

@jkotas
Copy link
Member

jkotas commented Oct 9, 2018

nuget can't be used in unity.

You can use nuget in Unity if you try hard. It is just not as straightforward as it should be. It is something you should be asking Unity to fix. It is not a good justification for adding APIs to the platform.

@Thaina
Copy link

Thaina commented Oct 9, 2018

@jkotas If we need to try hard to use it then defeat the purpose to use nuget as manager to make life easier. I could just copy dll directly or write my own code and that would be easier than trying hard to use nuget

If nuget is that important it should be in the core already. So anywhere using dotnet will be able to use nuget. It might be the reason that it not natively support from the start that unity haven't integrate it into their system. ValueTuple come later but we could use it in recent version of unity because it natively support

@Thaina
Copy link

Thaina commented Oct 9, 2018

@jkotas I think my side is the one who should say that, nuget is not a good justification for not adding APIs to the platform from the start. Core API should be just added when it is generic and high demand or people need to make a workaround to solve similar thing. Not just think that we have nuget so we could push anything in there. I just bring unity as the one example that nuget is not always available unlike core library

@jkotas
Copy link
Member

jkotas commented Oct 9, 2018

If nuget is that important it should be in the core already

nuget is a tool. It is not a runtime/framework API. I do not understand what you mean by having nuget in the core.

API should be just added when it is generic and high demand

You are basically saying that the most frequently used nuget packages should be added to the platform. The are two problems with that: It would kill the nuget ecosystem; and it would make the platform really big over time.

@Thaina
Copy link

Thaina commented Oct 9, 2018

@jkotas In my opinion, nuget is misused for hosting a small utility library that should be in the core

nuget is great and I use it on daily basis. But it actually should be used for third party service that required for some project, such as AWS and Google API or OpenCV or OpenGL. But not a core or module that more than 10% of project would like to use like JSON

In other words. If every 10 projects will have a project using one same library. That library should be a core module. Just think that if there is 100000 projects using CI system. Every time each project deployed it will put a strain on nuget server to download the same library

Unless nuget run on something like IPFS I will never think this approach is practical. Maybe we should have better modular system for the core library like this

If you think json should not be core library, System.Xml should also get out

@jkotas
Copy link
Member

jkotas commented Oct 9, 2018

100000 projects using CI system. Every time each project deployed it will put a strain on nuget server to download the same library

NuGet server is hosted in Azure. It can handle load like this just fine. Nothing to worry about.

@jnm2
Copy link
Contributor

jnm2 commented Oct 9, 2018

@Thaina NuGet is a fine solution for most things and this discussion seems to me to have gone off-topic.

What I see is that this API should be preferred to Task.WhenAll any time there are non-void results, as shown above. Existing BCL alternatives are not pretty, but they will dominate if no other option ships on equal footing with Task.WhenAll.

@Thaina
Copy link

Thaina commented Oct 9, 2018

@jnm2 What I trying to say is nuget package is not a good solution for this feature. It should exist in BCL along with Task.WhenAll because , as you said, in many situation it will really be preferable than Task.WhenAll. But because it not start as equal footing, it fragmented. Each person can make their own extension method and copy it everywhere. People would rely on this approach than nuget

nuget is a good solution for many things but not these kind of core functionality

@tarekgh
Copy link
Member

tarekgh commented Oct 9, 2018

To summarize the thread:

  • It looks using Nuget package is not bad choice. I know @Thaina don't like that because the difficulty of using Nuget with Unity, but this is really a generic issue need to be solved for Unity.
  • Although I agree with what @jnm2 mentioned regarding using Task.WhenAll pattern, but I think we can direct people to the Nuget package when we see such patterns in any of users code. And if we see many people really need that, we may consider it in the core later. I think this is reasonable middle ground for now.

@jnm2 what you think?

@Thaina
Copy link

Thaina commented Oct 10, 2018

@tarekgh I just use unity as the obvious example that nuget does not really available everywhere in C# platform unlike core BCL. In reality people always prefer copying code for these small functionality

Even @jnm2 himself admit that he does not use nuget for this

I haven't used it myself because I copy my gist into so many projects,

Do you think why? Because it really too small to rely on nuget. It is really the real situation in the world that nuget is not preferable in this functionality

If you just conclude it like that then you miss the whole point

@jnm2
Copy link
Contributor

jnm2 commented Oct 10, 2018

@tarekgh Seems fair. To me it's really a decision whether consuming results from WhenAll the current way seems tasteful to you all, and that's entirely your prerogative.

On the flip side, it is worth working uphill to change folks' reluctance to add a whole dependency to their project even if the alternative syntax is bad. It's another cross-cutting problem like Unity that needs to be worked independently.

I do appreciate you asking me. I've made my case and I'm happy to abide by your decision. 👍

@tarekgh
Copy link
Member

tarekgh commented Oct 10, 2018

Do you think why? Because it really too small to rely on nuget. It is really the real situation in the world that nuget is not preferable in this functionality

I think @jnm2 can answer this better but I think he may was not aware of the existence of this NuGet library. I don't believe nothing will prevent him of using the NuGet if decided to do so.

@jnm2 thanks for your reply. I am going to close the issue and we can open it if anything new come up in the future. it was a good discussion, at least it documented some helper code and pointed to the NuGet package too.

@tarekgh tarekgh closed this as completed Oct 10, 2018
@TylerBrinkley
Copy link
Contributor

Just discovered this issue, only 2 1/2 years late, but I agree this API shouldn't be added not because it wouldn't be useful to a great many people but because it has little to no visibility. If this had been implemented, I would not have known until looking to suggest something similar. As such I think this should instead be added as a Task.WhenAll overload, which is where people will look, where the argument would be a tuple of tasks so as not to conflict with the params array overloads.

@TylerBrinkley
Copy link
Contributor

I've added my proposal above to dotnet/corefx#25756.

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 3.0 milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 26, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Threading.Tasks
Projects
None yet
Development

No branches or pull requests