Replies: 32 comments
-
I think you could essentially do this with #1151, which is a more generalized approach. |
Beta Was this translation helpful? Give feedback.
-
@bondsbw |
Beta Was this translation helpful? Give feedback.
-
You'll need to define what your code should translate into. The more I look at it, the less value I see in this proposal, so perhaps I'm missing some key element. |
Beta Was this translation helpful? Give feedback.
-
Specifically are there really any differences from just this: async Task MyMethod()
{
var task1 = DoSomethingAsync();
var task2 = DoSomethingAsync();
var task3 = DoSomethingAsync();
await Task.WhenAny(task1, task2, task3);
// do something
await Task.WhenAll(task1, task2, task3);
// do something
} |
Beta Was this translation helpful? Give feedback.
-
I don't get why any of this needs this language ceremony that you're proposing. This is already quite easy to do and gets even easier with tuples and some extension methods, which are being proposed as additions to the BCL but you can add them yourself. https://github.com/dotnet/corefx/issues/16010 async Task MyMethod()
{
var task1 = DoSomethingAsync();
var task2 = DoSomethingAsync();
var task3 = DoSomethingAsync();
var (result1, result2, result3) = await (task1, task2, task3);
} |
Beta Was this translation helpful? Give feedback.
-
You cant divide any function into small parallel tasks. You must clarify what kind of tasks you are trying to run in paralell. Notice that maintaining multiple threads has its costs. paralellizing short running tasks may actually degrade performance. most modern processors have instruction pipelining mechanism to paralellize sequential instructions at hardware level in very efficient way, so if you are trying to parallelize small functions via multiple threads you are making more trouble than actually solving anything. you didnt explain how you use that syntax by the way. I dont understand how your code is supposed to run in those awaits and async blocks. |
Beta Was this translation helpful? Give feedback.
-
@MkazemAkhgary
|
Beta Was this translation helpful? Give feedback.
-
I see, so your goal here is to suggest a boatload of keywords specific to the purpose of trying to compile these "funclets" into separate pipelinable sets of instructions to be executed in parallel by some compatible CPU? How do you propose that actually work, especially since IL lacks any facility to accomplish that? |
Beta Was this translation helpful? Give feedback.
-
That syntax is akin to using |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Im saying that most CPUs are already designed to execute sequential operations very efficiently. Therefor trying to run small tasks in parallel at higher levels doesnt really work because you just introduce overhead to run each little task individually in different threads. You can batch many of these small tasks together and process a few batches in parallel. That way you can maximize performance.
Im not sure what you mean by that. Im not an expert. just wanted to make sure OP is not going in rabit hole. |
Beta Was this translation helpful? Give feedback.
-
Only if the compiler emits the machine language in an order that the CPU would recognize could be automatically parallelized. C# does not emit machine language. C# emits IL, an intermediate language which is abstracted away from any specific CPU. It is up to the CLR, at runtime, to then interpret or compile the IL into machine language for the target CPU and platform. This is why the same binary produced by C# can run on different OSes and CPUs without requiring recompilation.
As stated above, C# emits IL. The IL language has no concept of pipelines or parallelization. There would be nothing that the C# compiler could do here without coordinated changes in the CLR which the compiler would then utilize. However, these optimizations are pretty architecture specific and I doubt apply to general-purpose workloads. And even in the best case scenarios probably produce much less benefit for the amount of performance improvement that you might see. Such workloads are probably better expressed through special sets of types and method which specific JIT implementations could them optimize, like with SIMD. The IL produced by C# should express the intent, not the implementation. |
Beta Was this translation helpful? Give feedback.
-
I added a suggestion about SIMD Array Operations. I was thinking about how make loops performance better. Operations on numeric arrays can be SIMD in many cases, but other types of arrays and operations in larg loops can be parallelized. LinQ has AsParallel , but what about other normal loops?.. Can we have loops "AsParallel" somehow? This is why I suggested a new structure to make async more readable. Putting a loop un lambda and passing it to the Task.Run( ) may be a solution, but I prefer a more compact syntax. |
Beta Was this translation helpful? Give feedback.
-
TPL (in the BCL) comes with
I fully expect the programmer to explicitly state that intent as parallelism comes with a whole slew of caveats and frequently, applied incorrectly, will produce worse results than serialized. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour |
Beta Was this translation helpful? Give feedback.
-
@MohammadHamdyGhanem Yes it does. For arbitrary operations that you happen to want to run in parallel, you have |
Beta Was this translation helpful? Give feedback.
-
Such a thing exists. it's called "Task". For example, 'await' (or .ContinueWith) allows you to express dependencies. If you have more complex dependencies, then you can compose things more deeply with functionality like Task.WhenAll or Task.WhenAny. |
Beta Was this translation helpful? Give feedback.
-
Note: in the future it would be good to provide a lot more detail around what it is you are asking. The above request is far too vague as to understand what it is supposed to do and why it's superior to the existing library + existing language based approach to this problem space. |
Beta Was this translation helpful? Give feedback.
-
It's annoying to write code like this: async Task MyMethod()
{
var task1 = DoSomethingAsync();
var task2 = DoSomethingAsync();
var task3 = DoSomethingAsync();
await Task.WhenAll(task1, task2, task3);
var result1 = task1.Result;
var result2 = task2.Result;
var result3 = task3.Result;
} Even with a proposed solution to await on a tuple, the code looks weird when I have a lot of tasks, having a list of variables followed by a list of method calls that returns tasks: async Task MyMethod()
{
var (result1, result2, result3) = await (DoSomethingAsync1(), DoSomethingAsync2(), DoSomethingAsync3());
} We should be able to declare the variables and make the calls that returns the tasks sequentially, to read in this order:
instead of:
Something like: async Task MyMethod()
{
// it could use "when all" instead of just "when", but since I can't see a case when it makes sense to use "when any", I think it would be best to use just "when"
// it could use the keyword "parallel"
when (
var result1 = await DoSomethingAsync1(),
var result2 = await DoSomethingAsync2(),
var result3 = await DoSomethingAsync3(),
) {
return result1 + result2 + result3;
}
} |
Beta Was this translation helpful? Give feedback.
-
Why is: async Task MyMethod()
{
// it could use "when all" instead of just "when", but since I can't see a case when it makes sense to use "when any", I think it would be best to use just "when"
// it could use the keyword "parallel"
when (
var result1 = await DoSomethingAsync1(),
var result2 = await DoSomethingAsync2(),
var result3 = await DoSomethingAsync3(),
) {
return result1 + result2 + result3;
}
} better than just doing this: async Task MyMethod()
{
var t1= DoSomethingAsync1();
var t2= DoSomethingAsync2();
var t3 = DoSomethingAsync3();
return await t1 + await t1 + await t3;
} It's still just as parallel (since all tasks are launched at the same time), and the result only computes once all complete. It's simple and easy to read and understand, and it needs no new features. |
Beta Was this translation helpful? Give feedback.
-
For the sake of argument, the latter is worse as it doesn't bail immediately on the first failed task. So if the the first task takes 10 seconds and the last task takes 1 second but fails you end up having to wait for the full 10 seconds. That's why |
Beta Was this translation helpful? Give feedback.
-
For the sake of argument, i concede that that would be the case. So you could certainly do this as: async Task MyMethod()
{
var t1 = DoSomethingAsync1();
var t2 = DoSomethingAsync2();
var t3 = DoSomethingAsync3();
await Task.WhenAll(t1, t2, t3);
return await t1 + await t1 + await t3;
} if that really is a concern for your algorithm. -- Note: teh above is still only 5 lines, instead of needing 7 for the 'when+surrounding syntax'. |
Beta Was this translation helpful? Give feedback.
-
Also, if we get Declaration Expressions, this would simply be: async Task MyMethod()
{
await Task.WhenAll(
var t1 = DoSomethingAsync1(),
var t2 = DoSomethingAsync2(),
var t3 = DoSomethingAsync3());
return await t1 + await t1 + await t3;
} That seems like a much cleaner and more composable way of doing things, rather than making a 1-off feature just for parallel inline task execution. |
Beta Was this translation helpful? Give feedback.
-
I didn't realize that I can await the resolved task again after the But when I reuse the variable in a lot of places, the code gets dirty with all the Task<IList<MyType>> list1 = GetList1Async();
Task<IList<MyType>> list2 = GetList2Async();
await Task.WhenAll(list1, list2);
var list3 = (await list1).Except(await list2);
var list4 = (await list2).Select(e => new
{
FromList1 = (await list1).Select(x => x.SomeId = e.Id).ToList(),
Max = (await list1).Max(e => e.Value),
Count = (await list1).Count(),
}); |
Beta Was this translation helpful? Give feedback.
-
And when I use the variables inside a lot of lambdas, I have to mark all the lambdas as |
Beta Was this translation helpful? Give feedback.
-
So do this: var listTask1 = GetList1Async();
var listTask2 = GetList2Async();
await Task.WhenAll(listTask1, listTask2);
var list1 = await listTask1;
var list2 = await listTask2;
// use list1 and list 2 normally. |
Beta Was this translation helpful? Give feedback.
-
This repetition is exactly what I think that makes the code ugly. But I think I'll use the |
Beta Was this translation helpful? Give feedback.
-
@lmcarreiro You might be interested in https://www.nuget.org/packages/TaskTupleAwaiter/. var (result1, result2) = await (GetStringAsync(), GetGuidAsync());
var (policy, preferences) = await (
GetPolicyAsync(policyId, cancellationToken),
GetPreferencesAsync(cancellationToken)
).ConfigureAwait(false); |
Beta Was this translation helpful? Give feedback.
-
It's a big concern. If you don't await all tasks and one fails or cancels, you're effectively running the remaining tasks |
Beta Was this translation helpful? Give feedback.
-
As mentioned, if that is a concern, then you can easily handle it. I was just doing the general CPU-bound case where i would not expect failure. I regret having posted it since my intent was not to say that you had to write it in this fashion. Just that this would be sufficient for a great many cases of parallel work. I should have just written the robust solution to avoid these concerns. |
Beta Was this translation helpful? Give feedback.
-
Sure, and now that you've mentioned the CPU-bound case, I feel compelled to clarify that it's every bit as much a pitfall to run any kind of task in parallel without using WhenAll, including CPU-bound. This is the kind of thing that people often discover when it's too late. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I suggest this structure to make writing async tasks easier:
Note: the await block in optional to execute any code after the task completes. WhenAny, WhenEach and WhenAll are optional also.
The sync and a wsit can be removed if desling with direct pieces of code that excute imediately without waiting a response from long or slow process. Like internet resopnse.
this is useful in dividing any function into small parallel tasks to get use of the processor cores, without writing so many functions, each of then will be called one time only. This will encourage us to enhance any code by parallelism.
Operations on numeric arrays can be SIMD in many cases, but other types of arrays and operations in larg loops can be parallelized. LinQ has AsParallel , but what about other normal loops?.. Can we have loops "AsParallel" somehow? This is why I suggested a new structure to make async more readable. Putting a loop un lambda and passing it to the Task.Run( ) may be a solution, but I prefer a more compact syntax.
As I said, most of us now has a capable hardware, but most software doesn't make the most of it. If you have 4 loops, 3 ot them are independent but the forth depends on the results ot them, I don't expect you will think of using threads or async to optimize this code, because one minute delay for the user doesn't worth the effort. although the user can be frustrated! I see that easing writing parallel code to make it a habit for programmers worth the effort.
I don't mind to change the suggested structure to any better syntax. I only care about finding an easy fast way to tell the compiler how to divide the code into parallel parts, and what are the dependencies between them.
To prevent concurrency, c# should give warnings if more than one task tries to change the value of the same variable (or you can suggest a better action).
Beta Was this translation helpful? Give feedback.
All reactions