Create Target Cold Task

I have an async method after which I want to run another method. This works fine if I just call the method and add .ContinueWith ()

However, I have a new requirement that should only run the task if I can add it to the parallel dictionary.

I want to build a task, try to add it, and then run the task

However, it appears that Task.Start () immediately terminates the task, forcing the action to continue, and any wait ... does not wait.

can anyone explain why this is happening and the correct way to achieve my goal?

namespace UnitTestProject2 { [TestClass] public class taskProblem { [TestMethod] public void Test() { CancellationTokenSource cancel = new CancellationTokenSource(); ConcurrentDictionary<Guid, Task> tasks = new ConcurrentDictionary<Guid,Task>(); Guid id = Guid.NewGuid(); Task t = new Task(async () => await Get(), cancel.Token); t.ContinueWith(Complete); if (tasks.TryAdd(id, t)) { t.Start(); } else { //another thread is stopping stuff dont start new tasks } t.Wait(); //expected to wait for the get function to complete Console.WriteLine("end test"); } public async Task Get() { Console.WriteLine("start task"); await Task.Delay(10000); Console.WriteLine("end task"); } public void Complete(Task t) { Console.WriteLine("Complete"); } } } 

exit:

 start task end test Complete 

expected output:

 start task end task Complete end test 

Update:. It seems that there is no way to create a new task that will not immediately be launched or completed immediately in Task.Start?

+8
source share
3 answers

Your delegate is asynchronous. asynchronous void methods are fire and forget.

See The first paragraph Summary of patterns and anti-patterns: http://rarcher.azurewebsites.net/Post/PostContent/31

Maybe you can do something like this:

 [TestFixture] public class FIXTURENAMETests { [Test] public async Task NAME() { var tcs = new TaskCompletionSource<bool>(); Task t = LongRunningStuff(tcs); if (CanInsertInDictionary(t)) { tcs.SetResult(true); } else { tcs.SetException(new Exception()); } Trace.WriteLine("waiting for end"); try { await t; } catch (Exception exception) { Trace.WriteLine(exception); } Trace.WriteLine("end all"); } private bool CanInsertInDictionary(Task task) { return true; } private async Task LongRunningStuff(TaskCompletionSource<bool> tcs) { Trace.WriteLine("start"); try { await tcs.Task; } catch (Exception) { return; } Trace.WriteLine("do long running stuff"); await Task.Delay(10000); Trace.WriteLine("end"); } } 
+8
source

However, I have a new requirement - to start a task only if I can add it to the parallel dictionary.

Here is one possible solution to the above problem:

  • Completing Task<T> in Lazy<>
  • add GetOrAdd task to ConcurrentDictionary via GetOrAdd
  • use the output from GetOrAdd to wait for the task to complete

Thus, the task will not be launched while ConcurrentDictionary is doing internal work to handle conflicts, etc., And all subsequent await will use the same task object associated with this key in the dictionary.

Thus, the code below allows you to execute the task body only once for a given key in the dictionary in scenarios when several threads add tasks to the dictionary.

Here is the code:

 [TestMethod] public void ConcurrentMapLazyTask() { Func<Task> Get = async () => { Trace.WriteLine("Start task."); await Task.Delay(200); Trace.WriteLine("End task."); }; Action<Task> Complete = (t) => { Trace.WriteLine("Complete."); }; var mp = new ConcurrentDictionary<string, Lazy<Task>>(); Func<string, Lazy<Task>> valueFactory = (sKey) => { var s = Guid.NewGuid().ToString(); Trace.WriteLine(string.Format("valueFactory called => {0}", s)); return new Lazy<Task>(() => { Trace.WriteLine(string.Format("LazyTask factory called for {0}", s)); var t = Task.Run(async () => { Trace.WriteLine(string.Format("Task.Run executed for {0}", s)); await Get(); }); return Task.WhenAll(t, t.ContinueWith(Complete)); }); }; Func<Task> TestAsync = async () => { var lazyTask = mp.GetOrAdd("test", valueFactory); await lazyTask.Value; }; Action TestSync = () => { TestAsync().Wait(); Trace.WriteLine("End test."); }; Action TestSyncSlow = () => { Thread.Sleep(50); TestAsync().Wait(); Trace.WriteLine("End slow test."); }; //Parallel.Invoke(TestSync); Parallel.Invoke(TestSync, TestSyncSlow, TestSync, TestSync); } 

Here is the conclusion:

 valueFactory called => e35b0e9e-3326-41bb-b3b9-eb4e6dc37391 valueFactory called => e4929d93-4c77-4fbf-8e90-e8d6e4d7b009 LazyTask factory called for e35b0e9e-3326-41bb-b3b9-eb4e6dc37391 valueFactory called => 8863fb7f-1309-4c27-b805-71467022ac74 Task.Run executed for e35b0e9e-3326-41bb-b3b9-eb4e6dc37391 Start task. End task. Complete. End test. End test. End slow test. End test. 
  • Task.Run is executed only once.
  • Lazy factory was named only once
  • using the valueFactory parameter in GetOrAdd allowed to reduce the number of GetOrAdd objects created (3 pending wrappers resulting from 4 simultaneous calls to GetOrAdd )
0
source

Firstly, ContinueWith will return a new Task , you want to wait for the Complete method to Complete , but you are waiting for the first task t .

So, to bring Complete to end test , you have to wait for the second task:

 Task t = new Task(async () => await Get(), cancel.Token); // NOTE: t2 is a new Task returned from ContinueWith Task t2 = t.ContinueWith(Complete); if (tasks.TryAdd(id, t)) { t.Start(); } else { } // NOTE: Waiting on t2, NOT t t2.Wait(); Console.WriteLine("end test"); 

Now the output will be:

 start task Complete end test end task 

Well, this is still not the expected result. end task should be printed before Complete . This is because your asynchronous action is not expected: How to wait for an async delegate

I don’t know if I understood your requirements correctly. If so, I can do it like this:

Add a new supporting class:

 public class TaskEntry { public Task Task { get; set; } } 

Then change your code to:

 Guid id = Guid.NewGuid(); Task task = null; var entry = new TaskEntry(); if (tasks.TryAdd(id, entry)) { entry.Task = Get(); // Notice this line of code: task = entry.Task.ContinueWith(Complete); } if (task != null) { task.Wait(); } Console.WriteLine("end test"); 

Here I assume that TaskEntry will not be modified by other threads.

-1
source

All Articles