So I'm trying to build my app and I keep getting this error. I had to update my app for the new Unity Ads API, I also upgraded the unity editor while I was at it, and I've been getting this error ever since. I've tried doing what it suggests, but I have no idea why it's not working. Here's the full error:
WARNING:The option setting 'android.bundle.enableUncompressedNativeLibs=false' is deprecated.
The current default is 'true'.
It will be removed in version 8.0 of the Android Gradle plugin.
You can add the following to your build.gradle instead:
android {
packagingOptions {
jniLibs {
useLegacyPackaging = true
}
}
}
> Task :unityLibrary:preBuild UP-TO-DATE
> Task :launcher:preBuild UP-TO-DATE
> Task :unityLibrary:preReleaseBuild UP-TO-DATE
> Task :launcher:preReleaseBuild UP-TO-DATE
> Task :unityLibrary:packageReleaseRenderscript NO-SOURCE
> Task :unityLibrary:compileReleaseRenderscript NO-SOURCE
> Task :launcher:generateReleaseResValues
> Task :launcher:compileReleaseRenderscript NO-SOURCE
> Task :unityLibrary:generateReleaseResValues
> Task :launcher:generateReleaseResources
> Task :unityLibrary:generateReleaseResources
> Task :launcher:writeReleaseApplicationId
> Task :launcher:analyticsRecordingRelease
> Task :launcher:createReleaseCompatibleScreenManifests
> Task :launcher:extractDeepLinksRelease
> Task :unityLibrary:packageReleaseResources
> Task :unityLibrary:extractDeepLinksRelease
> Task :launcher:checkReleaseDuplicateClasses
> Task :unityLibrary:compileReleaseAidl NO-SOURCE
> Task :unityLibrary:writeReleaseAarMetadata
> Task :unityLibrary:compileReleaseLibraryResources
> Task :unityLibrary:processReleaseManifest
> Task :launcher:compileReleaseAidl NO-SOURCE
> Task :launcher:generateReleaseBuildConfig
> Task :unityLibrary:generateReleaseBuildConfig
> Task :launcher:javaPreCompileRelease
> Task :unityLibrary:javaPreCompileRelease
> Task :launcher:checkReleaseAarMetadata
> Task :launcher:mergeReleaseResources
> Task :unityLibrary:mergeReleaseShaders
> Task :unityLibrary:compileReleaseShaders NO-SOURCE
> Task :unityLibrary:generateReleaseAssets UP-TO-DATE
> Task :unityLibrary:parseReleaseLocalResources
> Task :launcher:processReleaseMainManifest
> Task :launcher:mapReleaseSourceSetPaths
> Task :launcher:processReleaseManifest
> Task :launcher:processApplicationManifestReleaseForBundle
> Task :launcher:mergeReleaseShaders
> Task :launcher:compileReleaseShaders NO-SOURCE
> Task :launcher:generateReleaseAssets UP-TO-DATE
> Task :launcher:processReleaseJavaRes NO-SOURCE
> Task :launcher:mergeReleaseJniLibFolders
> Task :launcher:writeReleaseAppMetadata
> Task :launcher:collectReleaseDependencies
> Task :launcher:configureReleaseDependencies
> Task :launcher:parseReleaseIntegrityConfig
> Task :launcher:validateSigningRelease
> Task :launcher:processReleaseManifestForPackage
> Task :launcher:bundleReleaseResources
> Task :unityLibrary:packageReleaseAssets
> Task :unityLibrary:processReleaseJavaRes
> Task :unityLibrary:bundleLibResRelease
> Task :unityLibrary:generateReleaseRFile
> Task :launcher:mergeReleaseAssets
> Task :unityLibrary:mergeReleaseJniLibFolders
> Task :unityLibrary:prepareReleaseArtProfile
> Task :launcher:mergeReleaseArtProfile
> Task :unityLibrary:mergeReleaseNativeLibs
> Task :launcher:processReleaseResources
> Task :launcher:mergeReleaseJavaResource
> Task :unityLibrary:compileReleaseJavaWithJavac FAILED
>45 actionable tasks: 45 executed
UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&) (at /Users/bokken/buildslave/unity/build/Modules/IMGUI/GUIUtility.cs:196)```
it looked to me like the meat of the error was in the top bit, but again, I tried doing what it suggested and it just turns out the same error every time. I have absolutely no idea how to fix this and some help would be very much appreciated!
Related
I created a task like the following code
Task task = new(async () =>
{
// without await Task Delay dont work
await Task.Delay(TimeSpan.FromSeconds(5));
Console.WriteLine("Task is down");
});
task.Start();
var awaiter = task.GetAwaiter();
awaiter.OnCompleted(() =>
{
Console.WriteLine("Task is Completed");
});
Why the task completed first is printed and then the task is down
The task is complete and will be printed if my operation is not finished yet
Because Task constructors are not async aware. No overloads take an function returning a Task so the task created via constructor and async lambda will finish as soon as first await (of unfinished task) is encountered.
And in general you should try avoid using Task constructors and prefer Task.Run instead. From the docs:
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.
I have an IAsyncEnumerable<string> stream that contains data downloaded from the web, and I want to save asynchronously each piece of data in a SQL database. So I used the ForEachAwaitAsync extension method from the System.Linq.Async library. My problem is that downloading and saving each piece of data is happening sequentially, while I would prefer if it happened concurrently.
To clarify, I don't want to download more than one pieces of data at the same time, neither I want to save more than one pieces of data at the same time. What I want is that while I am saving a piece of data in the database, the next piece of data should be concurrently downloaded from the web.
Below is a minimal (contrived) example of my current solution. Five items are downloaded and then are saved in the database. Downloading each item takes 1 second, and saving it takes another 1 second:
async IAsyncEnumerable<string> GetDataFromWeb()
{
foreach (var item in Enumerable.Range(1, 5))
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} > Downloading #{item}");
await Task.Delay(1000); // Simulate an I/O-bound operation
yield return item.ToString();
}
}
var stopwatch = Stopwatch.StartNew();
await GetDataFromWeb().ForEachAwaitAsync(async item =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} > Saving #{item}");
await Task.Delay(1000); // Simulate an I/O-bound operation
});
Console.WriteLine($"Duration: {stopwatch.ElapsedMilliseconds:#,0} msec");
The code is working, but not in the way I want. The total duration is ~10 seconds, instead of the desirable ~6 seconds.
Actual undesirable output:
04:55:50.526 > Downloading #1
04:55:51.595 > Saving #1
04:55:52.598 > Downloading #2
04:55:53.609 > Saving #2
04:55:54.615 > Downloading #3
04:55:55.616 > Saving #3
04:55:56.617 > Downloading #4
04:55:57.619 > Saving #4
04:55:58.621 > Downloading #5
04:55:59.622 > Saving #5
Duration: 10,115 msec
Hypothetical desirable output:
04:55:50.000 > Downloading #1
04:55:51.000 > Saving #1
04:55:51.000 > Downloading #2
04:55:52.000 > Saving #2
04:55:52.000 > Downloading #3
04:55:53.000 > Saving #3
04:55:53.000 > Downloading #4
04:55:54.000 > Saving #4
04:55:54.000 > Downloading #5
04:55:55.000 > Saving #5
Duration: 6,000 msec
I am thinking about implementing a custom extension method named ForEachConcurrentAsync, having identical signature with the aforementioned ForEachAwaitAsync method, but with behavior that allows enumerating and acting on items to occur concurrently. Below is a stub of this method:
/// <summary>
/// Invokes and awaits an asynchronous action on each element in the source sequence.
/// Each action is awaited concurrently with fetching the sequence's next element.
/// </summary>
public static Task ForEachConcurrentAsync<T>(
this IAsyncEnumerable<T> source,
Func<T, Task> action,
CancellationToken cancellationToken = default)
{
// What to do?
}
How could this functionality be implemented?
Additional requirements:
Leaking running tasks in case of cancellation or failure is not acceptable. All started tasks should be completed when the method completes.
In the extreme case that both the enumeration and an action fails, only one of the two exceptions should be propagated, and either one is OK.
The method should be genuinely asynchronous, and should not block the current thread (unless the action parameter contains blocking code, but this is a responsibility of the caller to prevent).
Clarifications:
In case saving the data takes longer than downloading them from the web, the method should not keep downloading more items in advance. Only one piece of data should be downloaded in advance at maximum, while the previous one is saved.
The IAsyncEnumerable<string> with the web data is the starting point of this problem. I don't want to change the generator method of the IAsyncEnumerable<string>. I want to act on its elements (by saving them into the database), while the enumerable is enumerated.
It sounds like you just need to keep track of the previous action's Task and await it before the next action Task.
public static async Task ForEachConcurrentAsync<T>(
this IAsyncEnumerable<T> source,
Func<T, Task> action,
CancellationToken cancellationToken = default)
{
Task previous = null;
try
{
await source.ForEachAwaitAsync(async item =>
{
if(previous != null)
{
await previous;
}
previous = action(item);
});
}
finally
{
if(previous != null)
{
await previous;
}
}
}
All that's left is to sprinkle in the cancellation code.
Here is my solution.
I had to change the sequence to an array to access the next element.
Not sure if it fits your requirements to populate an array.
The idea is to start downloading the next item before returning the current.
private static async Task Main(string[] args)
{
var stopwatch = Stopwatch.StartNew();
await foreach (var item in GetDataFromWebAsync())
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} > Saving #{item}");
await Task.Delay(1000); // Simulate an I/O-bound operation
}
Console.WriteLine($"Duration: {stopwatch.ElapsedMilliseconds:#,0} msec");
}
private static async IAsyncEnumerable<string> GetDataFromWebAsync()
{
var items = Enumerable
.Range(1, 5)
.Select(x => x.ToString())
.ToArray();
Task<string> next = null;
for (var i = 0; i < items.Length; i++)
{
var current = next is null
? await DownloadItemAsync(items[i])
: await next;
var nextIndex = i + 1;
next = StarNextDownloadAsync(items, nextIndex);
yield return current;
}
}
private static async Task<string> StarNextDownloadAsync(IReadOnlyList<string> items, int nextIndex)
{
return nextIndex < items.Count
? await DownloadItemAsync(items[nextIndex])
: null;
}
private static async Task<string> DownloadItemAsync(string item)
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} > Downloading #{item}");
await Task.Delay(1000);
return item;
}
Console Output:
15:57:26.226 > Downloading #1
15:57:27.301 > Downloading #2
15:57:27.302 > Saving #1
15:57:28.306 > Downloading #3
15:57:28.307 > Saving #2
15:57:29.312 > Downloading #4
15:57:29.340 > Saving #3
15:57:30.344 > Downloading #5
15:57:30.347 > Saving #4
15:57:31.359 > Saving #5
Duration: 6 174 msec
Here is a relatively simple implementation that does not depend on the System.Linq.Async package:
/// <summary>
/// Invokes and awaits an asynchronous action on each element in the source sequence.
/// Each action is awaited concurrently with fetching the sequence's next element.
/// </summary>
public static async Task ForEachConcurrentAsync<T>(
this IAsyncEnumerable<T> source,
Func<T, Task> action,
CancellationToken cancellationToken = default)
{
var enumerator = source.GetAsyncEnumerator(cancellationToken);
await using (enumerator.ConfigureAwait(false))
{
if (!await enumerator.MoveNextAsync().ConfigureAwait(false)) return;
while (true)
{
Task task = action(enumerator.Current);
bool moved;
try { moved = await enumerator.MoveNextAsync().ConfigureAwait(false); }
finally { await task.ConfigureAwait(false); }
if (!moved) break;
}
}
}
Instead of awaiting both concurrent tasks with a Task.WhenAll, a try/finally block is used for simplicity. The downside is that if both concurrent operations fail, the error of the MoveNextAsync will not be propagated.
I am running an infinite loop of updates using Task.Run. This is the code:
Task.Run( async () => {
while(true){
Thread.Sleep(10000);
checkForUpdates();
}
});
In the old days, I used to write special code for long running tasks. Does the duration of of a task execution matter with Task.Run? Is there a timeout built in?
No ! there is no built-in timeout for Task.Run(). That means it will continue running until you manually cancel it. And you can implement that by doing something like this.
int timeout = 1000;
var task = SomeOperationAsync(cancellationToken);
if (await Task.WhenAny(task, Task.Delay(timeout, cancellationToken)) == task)
{
// Task completed within timeout.
// Consider that the task may have faulted or been canceled.
// We re-await the task so that any exceptions/cancellation is rethrown.
await task;
}
else
{
// timeout/cancellation logic
}
you can checkout this blog for more understanding Crafting a task time-out
It's not possible to await a List<Task> that's changing because Task.WhenAny(List<Task>) takes a copy of the List<Task>.
What's an appropriate pattern for
List<Task> taskList = new List<Task>();
await Task.WhenAny(taskList);
When taskList could have other tasks added to it after the first WhenAny is called?
Full demo code below demonstrating the issue.
static readonly List<Task<int>> taskList = new List<Task<int>>();
static readonly Random rnd = new Random(1);
static async Task<int> RunTaskAsync(int taskID,int taskDuration)
{
await Task.Yield();
Console.WriteLine("Starting Task: {0} with a duration of {1} seconds", taskID, taskDuration / 1000);
await Task.Delay(taskDuration); // mimic some work
return taskID;
}
static async Task AddTasksAsync(int numTasks, int minDelay, int maxDelay)
{
// Add numTasks asyncronously to the taskList
// First task is added Syncronously and then we yield the adds to a worker
taskList.Add(RunTaskAsync(1, 60000)); // Make the first task run for 60 seconds
await Task.Delay(5000); // wait 5 seconds to ensure that the WhenAny is started with One task
// remaing task run's are Yielded to a worker thread
for (int i = 2; i <= numTasks; i++)
{
await Task.Delay(rnd.Next(minDelay, maxDelay));
taskList.Add(RunTaskAsync(i, rnd.Next(5, 30) * 1000));
}
}
static async Task Main(string[] args)
{
Stopwatch sw = new Stopwatch(); sw.Start();
// Start a Fire and Forget Task to create some running tasks
var _ = AddTasksAsync(10, 1, 3000);
// while there are tasks to complete use the main thread to process them as they comeplete
while(taskList.Count > 0)
{
var t = await Task.WhenAny(taskList);
taskList.Remove(t);
var i = await t;
Console.WriteLine("Task {0} found to be completed at: {1}",i,sw.Elapsed);
}
// All tasks have completed sucessfully - exit main thread
}
Console Output, showing that the WhenAny() loop found all the other tasks completed, only after finding and removing the 60 second task.
Starting Task: 1 with a duration of 60 seconds
Starting Task: 2 with a duration of 7 seconds
Starting Task: 3 with a duration of 24 seconds
Starting Task: 4 with a duration of 15 seconds
Starting Task: 5 with a duration of 28 seconds
Starting Task: 6 with a duration of 21 seconds
Starting Task: 7 with a duration of 11 seconds
Starting Task: 8 with a duration of 29 seconds
Starting Task: 9 with a duration of 21 seconds
Starting Task: 10 with a duration of 20 seconds
Task 1 found to be completed at: 00:01:00.1305811
Task 2 found to be completed at: 00:01:00.1312951
Task 3 found to be completed at: 00:01:00.1315689
Task 4 found to be completed at: 00:01:00.1317623
Task 5 found to be completed at: 00:01:00.1319427
Task 6 found to be completed at: 00:01:00.1321225
Task 7 found to be completed at: 00:01:00.1323002
Task 8 found to be completed at: 00:01:00.1324379
Task 9 found to be completed at: 00:01:00.1325962
Task 10 found to be completed at: 00:01:00.1327377
Thanks!
There's a problem with the code you've shown, namely it doesn't have a sensible communication pipeline between the worker and the task creator. You need some kind of a messaging mechanism to notify the worker about new tasks (and when there are no more tasks) so that it can react to it. That is something you have to figure out for your concurrent system and the exact implementation is tangential to the question, so I'll just assume we have OnTaskAdded(Task task) and OnEnd() methods in our worker.
From what you're saying, you don't want to really wait until any task completes, but rather for each task execute something when it completes. SEE UPDATED ANSWER BELOW.
That can be achieved with ContinueWith:
class Worker
{
private List<Task> _tasks = new List<Task>();
private readonly Stopwatch _stopwatch = new Stopwatch();
// Start the stopwatch in the constructor or in some kind of a StartProcessing method.
void OnTaskAdded(Task<int> task)
{
var taskWithContinuation = task.ContinueWith(t =>
Console.WriteLine("Task {0} found to be completed at: {1}", t.Result, _stopwatch.Elapsed));
_tasks.Add(taskWithContinuation);
}
async Task OnEndAsync()
{
// We're finishing work and there will be no more tasks, it's safe to await them all now.
await Task.WhenAll(_tasks);
}
}
EDIT:
After all that moralizing talk about ensuring a sensible messaging pipeline, I reckoned I can actually give you a quick-and-dirty implementation just so you can see it works:
// DISCLAIMER: NOT PRODUCTION CODE!!!
public static async Task Main()
{
Stopwatch sw = new Stopwatch(); sw.Start();
// Start a Fire and Forget Task to create some running tasks
var _ = AddTasksAsync(10, 1, 3000);
var internalList = new List<Task>();
// while there are tasks to complete use the main thread to process them as they comeplete
var i = 0;
while (i < 10)
{
while (taskList.Count <= i)
{
// No new tasks, check again after a delay -- THIS IS VERY BAD!
await Task.Delay(100);
}
Console.WriteLine("Task {0} intercepted at: {1}", i + 1, sw.Elapsed);
var taskWithContinuation = taskList[i].ContinueWith(t =>
Console.WriteLine("Task {0} found to be completed at: {1}", t.Result, sw.Elapsed));
internalList.Add(taskWithContinuation);
++i;
}
await Task.WhenAll(internalList);
}
Let me stress that again: this is not production-quality code! Actively waiting for more tasks, ugh. Its output is something like this:
Task 1 intercepted at: 00:00:00.0495570
Starting Task: 1 with a duration of 60 seconds
Starting Task: 2 with a duration of 7 seconds
Task 2 intercepted at: 00:00:05.8459622
Starting Task: 3 with a duration of 24 seconds
Task 3 intercepted at: 00:00:07.2626124
Starting Task: 4 with a duration of 15 seconds
Task 4 intercepted at: 00:00:09.2257285
Starting Task: 5 with a duration of 28 seconds
Task 5 intercepted at: 00:00:10.3058738
Starting Task: 6 with a duration of 21 seconds
Task 6 intercepted at: 00:00:10.6376981
Starting Task: 7 with a duration of 11 seconds
Task 7 intercepted at: 00:00:10.7507146
Starting Task: 8 with a duration of 29 seconds
Task 8 intercepted at: 00:00:11.7107754
Task 2 found to be completed at: 00:00:12.8111589
Starting Task: 9 with a duration of 21 seconds
Task 9 intercepted at: 00:00:13.7883430
Starting Task: 10 with a duration of 20 seconds
Task 10 intercepted at: 00:00:14.6707959
Task 7 found to be completed at: 00:00:21.6692276
Task 4 found to be completed at: 00:00:24.2125638
Task 3 found to be completed at: 00:00:31.2276640
Task 6 found to be completed at: 00:00:31.5908324
Task 10 found to be completed at: 00:00:34.5585143
Task 9 found to be completed at: 00:00:34.7053864
Task 5 found to be completed at: 00:00:38.2616534
Task 8 found to be completed at: 00:00:40.6372696
Task 1 found to be completed at: 00:01:00.0720695
You can see that lines are a little bit shuffled due to the nature of multithreaded work, but the timestamps are accurate.
UPDATE:
Well, I'm pretty dumb, I've just invited you into an anti-pattern. Using ContinueWith is dangerous, moreover it's overcomplicated - async/await was introduced to free us of manually scheduling continuations. You can just wrap your Task<int> with an operation that awaits it and logs the time.
class Worker
{
private List<Task> _tasks = new List<Task>();
private readonly Stopwatch _stopwatch = new Stopwatch();
// Start the stopwatch in the constructor or in some kind of a StartProcessing method.
void OnTaskAdded(Task<int> task)
{
var taskWithContinuation = ContinueWithLog(task);
_tasks.Add(taskWithContinuation);
}
async Task OnEndAsync()
{
// We're finishing work and there will be no more tasks, it's safe to await them all now.
await Task.WhenAll(_tasks);
}
private Task ContinueWithLog(Task<int> task)
{
var i = await source;
Console.WriteLine("Task {0} found to be completed at: {1}", i, sw.Elapsed);
}
}
Using your example code for a quick-and-dirty PoC:
class Program
{
static readonly List<Task<int>> taskList = new List<Task<int>>();
static readonly Random rnd = new Random(1);
static readonly Stopwatch sw = new Stopwatch();
static async Task<int> RunTaskAsync(int taskID, int taskDuration)
{
await Task.Yield();
Console.WriteLine("Starting Task: {0} with a duration of {1} seconds", taskID, taskDuration / 1000);
await Task.Delay(taskDuration); // mimic some work
return taskID;
}
static async Task AddTasksAsync(int numTasks, int minDelay, int maxDelay)
{
// Add numTasks asyncronously to the taskList
// First task is added Syncronously and then we yield the adds to a worker
taskList.Add(RunTaskAsync(1, 60000)); // Make the first task run for 60 seconds
await Task.Delay(5000); // wait 5 seconds to ensure that the WhenAny is started with One task
// remaing task run's are Yielded to a worker thread
for (int i = 2; i <= numTasks; i++)
{
await Task.Delay(rnd.Next(minDelay, maxDelay));
taskList.Add(RunTaskAsync(i, rnd.Next(5, 30) * 1000));
}
}
public static async Task ContinueWithLog(Task<int> source)
{
var i = await source;
Console.WriteLine("Task {0} found to be completed at: {1}", i, sw.Elapsed);
}
public static async Task Main()
{
sw.Start();
// Start a Fire and Forget Task to create some running tasks
var _ = AddTasksAsync(10, 1, 3000);
var internalList = new List<Task>();
// while there are tasks to complete use the main thread to process them as they comeplete
var i = 0;
while (i < 10)
{
while (taskList.Count <= i)
{
// No new tasks, check again after a delay -- THIS IS VERY BAD!
await Task.Delay(100);
}
Console.WriteLine("Task {0} intercepted at: {1}", i + 1, sw.Elapsed);
internalList.Add(ContinueWithLog(taskList[i]));
++i;
}
await Task.WhenAll(internalList);
}
}
Output:
Starting Task: 1 with a duration of 60 seconds
Task 1 intercepted at: 00:00:00.0525006
Starting Task: 2 with a duration of 7 seconds
Task 2 intercepted at: 00:00:05.8551382
Starting Task: 3 with a duration of 24 seconds
Task 3 intercepted at: 00:00:07.2687049
Starting Task: 4 with a duration of 15 seconds
Task 4 intercepted at: 00:00:09.2404507
Starting Task: 5 with a duration of 28 seconds
Task 5 intercepted at: 00:00:10.3325019
Starting Task: 6 with a duration of 21 seconds
Task 6 intercepted at: 00:00:10.6654663
Starting Task: 7 with a duration of 11 seconds
Task 7 intercepted at: 00:00:10.7809841
Starting Task: 8 with a duration of 29 seconds
Task 8 intercepted at: 00:00:11.7576237
Task 2 found to be completed at: 00:00:12.8151955
Starting Task: 9 with a duration of 21 seconds
Task 9 intercepted at: 00:00:13.7228579
Starting Task: 10 with a duration of 20 seconds
Task 10 intercepted at: 00:00:14.5829039
Task 7 found to be completed at: 00:00:21.6848699
Task 4 found to be completed at: 00:00:24.2089671
Task 3 found to be completed at: 00:00:31.2300136
Task 6 found to be completed at: 00:00:31.5847257
Task 10 found to be completed at: 00:00:34.5550722
Task 9 found to be completed at: 00:00:34.6904076
Task 5 found to be completed at: 00:00:38.2835777
Task 8 found to be completed at: 00:00:40.6445029
Task 1 found to be completed at: 00:01:00.0826952
This is the idiomatic way to achieve what you want. I'm sorry for misleading you with ContinueWith first, it's unnecessary and error-prone, now we both know.
A List<Task> is not a suitable container for this kind of job because it does not support the notion of Completion. So you won't be able to determine if there are more tasks to be added in the list, so that you can stop waiting. There are multiple alternatives though.
BlockingCollection<Task>. The producer calls the methods Add and finally CompleteAdding, to signal that has finished adding tasks. The consumer just enumerates the GetConsumingEnumerable. Very simple, but blocking by nature (not async).
BufferBlock<Task>. The producer calls the methods SendAsync and finally Complete, to signal that has finished adding tasks. The consumer enumerates asynchronously using the methods OutputAvailableAsync and TryReceive. Requires the package TPL Dataflow (for .NET Framework, it's included in .NET Core).
Channel<Task>. The producer calls the methods Writer.WriteAsync and finally Writer.Complete to signal that has finished adding tasks. The consumer enumerates asynchronously using the methods Reader.WaitToReadAsync and Reader.TryRead. Requires the package System.Threading.Channels (for .NET Framework, it's included in .NET Core).
IObservable<Task> + IObserver<Task> pair. The observer subscribes to the observable, and then starts receiving notifications about new tasks. The last notification is the onCompleted(), that signals that no more notifications are going to be produced. The Reactive Extensions library includes a tone of methods for manipulating observables, and one of them is the Merge operator, that can be used for awaiting all the tasks, exploiting the fact that a Task<T> can be transformed to an IObservable<T> that produces a singe onNext notification. This approach may seem quite eccentric, and it probably doesn't worth the investment of learning this technology (the reactive programming paradigm), unless you are dealing frequently with incoming streams of data that you would like to filter, transform, combine etc.
Update: In retrospect the first three options cannot be used as is, because you also want to await the tasks. So my suggestion now is to use a TransformBlock<Task, Task> instead of a BufferBlock<Task>.
var block = new TransformBlock<Task, Task>(async task =>
{
try
{
await task;
}
catch { } // suppress exceptions
return task;
});
Example of a producer that adds tasks to the block:
var producer = Task.Run(async () =>
{
for (int i = 1; i <= 10; i++)
{
await Task.Delay(100);
Console.WriteLine($"Sending {i}");
await block.SendAsync(Task.Delay(i * 100));
}
block.Complete();
});
Example of a consumer that receives the completed tasks from the block:
var consumer = Task.Run(async () =>
{
while (await block.OutputAvailableAsync())
{
while (block.TryReceive(out var task))
{
Console.WriteLine($"Task Completed: {task.Status}");
}
}
});
The tasks are received in the same order they were added in the block. If you want to receive them as soon as they are completed, configure the block like this:
new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = Int32.MaxValue,
EnsureOrdered = false
}
Task.WhenAll(IEnumerable<Task>) waits for all tasks in the IEnumerable are complete --- but only the tasks in the list when it's first called. If any active task adds to the list, they aren't considered. This short example demonstrates:
List<Task> _tasks = new List<Task>();
public async Task QuickExample()
{
for(int n =0; n < 6; ++n)
_tasks.Add(Func1(n));
await Task.WhenAll(_tasks);
Console.WriteLine("Some Tasks complete");
await Task.WhenAll(_tasks);
Console.WriteLine("All Tasks complete");
}
async Task Func1(int n)
{
Console.WriteLine($"Func1-{n} started");
await Task.Delay(2000);
if ((n % 3) == 1)
_tasks.Add(Func2(n));
Console.WriteLine($"Func1-{n} complete");
}
async Task Func2(int n)
{
Console.WriteLine($"Func2-{n} started");
await Task.Delay(2000);
Console.WriteLine($"Func2-{n} complete");
}
This outputs:
Func1-0 started
Func1-1 started
Func1-2 started
Func1-3 started
Func1-4 started
Func1-5 started
Func1-5 complete
Func1-3 complete
Func2-1 started
Func1-1 complete
Func1-0 complete
Func1-2 complete
Func2-4 started
Func1-4 complete
Some Tasks complete
Func2-4 complete
Func2-1 complete
All Tasks complete
Done
The second Task.WhenAll() solves the problem in this case, but that's a rather fragile solution. What's the best way to handle this in the general case?
You are modifying the List<> without locking it... You like to live a dangerous life :-) Save the Count of the _tasks before doing a WaitAll, then after the WaitAll check the Count of _tasks. If it is different, do another round (so you need a while around the WaitAll.
int count = _tasks.Count;
while (true)
{
await Task.WhenAll(_tasks);
lock (_tasks)
{
if (count == _tasks.Count)
{
Console.WriteLine("All Tasks complete");
break;
}
count = _tasks.Count;
Console.WriteLine("Some Tasks complete");
}
}
async Task Func1(int n)
{
Console.WriteLine($"Func1-{n} started");
await Task.Delay(2000);
if ((n % 3) == 1)
{
lock (_tasks)
{
_tasks.Add(Func2(n));
}
}
Console.WriteLine($"Func1-{n} complete");
}
I'll add a second (probably more correct solution), that is different from what you are doing: you could simply await the new Tasks from the Tasks that generated them, without cascading them to the _tasks collection. If A creates B, then A doesn't finish until B finishes. Clearly you don't need to add the new Tasks to the _tasks collection.
Asynchronous function will return to the caller on first await.
So for loop will be complete before you add extra tasks to original tasks list.
Implementation of Task.WhenAll will iterate/copy tasks to local list, so added tasks after Task.WhenAll called will be ignored.
In your particular case moving call to Func1 before await Task.Delay() could be a solution.
async Task Func1(int n)
{
Console.WriteLine($"Func1-{n} started");
if ((n % 3) == 1)
_tasks.Add(Func2(n));
await Task.Delay(2000);
Console.WriteLine($"Func1-{n} complete");
}
But if in real scenario calling of Func2 depend on result of some asynchronous method, then you need some other solution.
Since it seems that additional tasks can be created during the course of executing the original list of tasks, you will need a simple while construct.
while (_tasks.Any( t => !t.IsCompleted )
{
await Task.WhenAll(_tasks);
}
This will check the list for any uncompleted tasks and await them until it catches the list at a moment when there are no tasks left.
Consider this; it sounds like work is being submitted to the "Task List" from another thread. In a sense, the "task submission" thread itself could also be yet another Task for you to wait on.
If you wait for all Tasks to be submitted, then you are guaranteed that your next call to WhenAll will yield a fully-completed payload.
Your waiting function could/should be a two-step process:
Wait for the "Task Submitting" task to complete, signalling all Tasks are submitted
Wait for all the submitted tasks to complete.
Example:
public async Task WaitForAllSubmittedTasks()
{
// Work is being submitted in a background thread;
// Wrap that thread in a Task, and wait for it to complete.
var workScheduler = GetWorkScheduler();
await workScheduler;
// All tasks submitted!
// Now we get the completed list of all submitted tasks.
// It's important to note: the submitted tasks
// have been chugging along all this time.
// By the time we get here, there are probably a number of
// completed tasks already. It does not delay the speed
// or execution of our work items if we grab the List
// after some of the work has been completed.
//
// It's entirely possible that - by the time we call
// this function and wait on it - almost all the
// tasks have already been completed!
var submittedWork = GetAllSubmittedTasks();
await Task.WhenAll(submittedWork);
// Work complete!
}