var tasks = new List<Task>();
for (int i = 0; i < pageCount; i++)
{
var task = Task.Run(() =>
{
worker.GetHouses(currentPage);
});
tasks.Add(task);
currentPage++;
}
Task.WaitAll(tasks.ToArray());
There is something i don't understand.
Whenever i use:
var tasks = new[]
{
Task.Run(() => {worker.GetHouses(1);}),
Task.Run(() => {worker.GetHouses(2);}),
Task.Run(() => {worker.GetHouses(3);})
};
And i loop trough that array, i get results perfectly fine. (when using Task.WaitAll(tasks)
When i use:
var tasks = new List<Task>();
my Task.WaitAll(tasks.toArray()) doesn't seem to work, my tasks "Status" stays on "RanToCompletion"
What did i do wrong?
You have a synchronization problem with the currentPage variable. Also create tasks with result.
Solution:
var tasks = new List<Task<List<House>>>();
for (int i = 0; i < pageCount; i++)
{
var currentPageCopy = currentPage;
var task = Task.Run(() =>
{
return worker.GetHouses(currentPageCopy);
});
tasks.Add(task);
currentPage++;
}
Task.WaitAll(tasks.ToArray());
The problem with your code is that all GetHouses invocations will be called with currentPage + pageCount - 1 as the last value will be used for all method calls...
There's been little issue with task types.
In your sample you were using System.Threading.Tasks.Task, which does not have the result - it's intended just to do some job, like void method.
In your code here:
var tasks = new[]
{
Task.Run(() => {worker.GetHouses(1);}),
Task.Run(() => {worker.GetHouses(2);}),
Task.Run(() => {worker.GetHouses(3);})
};
no type were specified explicitly, so it turned out to be System.Threading.Tasks.Task<List<House>>, but first piece of code you specified the System.Threading.Tasks.Task explicitly:
var tasks = new List<Task>();
What you need to use is System.Threading.Tasks.Task<TResult>:
var tasks = new List<Task<List<House>>>();// <- task type specified explicitly
for (int i = 0; i < pageCount; i++)
{
var task = Task.Factory.StartNew<List<House>>(() =>// <- task type specified explicitly , though it's mandatory here
{
return worker.GetHouses(currentPage);
});
tasks.Add(task);
currentPage++;
}
In similar situations I tend to define types explicitly, so that code becomes clearer to read and as you can see, even to work.
Related
I'm trying to execute multiple requests at the same time to a Pi Number API. The main problem is that despite the 'Task.WhenAll(ExecuteRequests()).Wait();' line, it isn't completing all tasks. It should execute 50 requests and add it results to pi Dictionary, but after code execution the dictionary has about 44~46 items.
I tried to add an 'availables threads at ThreadPool verification', so i could guarantee i have enough Threads, but nothing changed.
The other problem is that sometimes when I run the code, i have an error saying I'm trying to add an already added key to the dicitionary, but the lock statement wasn't supposed to guarantee this error doesn't occur?
const int TotalRequests = 50;
static int requestsCount = 0;
static Dictionary<int, string> pi = new();
static readonly object lockState = new();
static void Main(string[] args)
{
var timer = new Stopwatch();
timer.Start();
Task.WhenAll(ExecuteRequests()).Wait();
timer.Stop();
foreach (var item in pi.OrderBy(x => x.Key))
Console.Write(item.Value);
Console.WriteLine($"\n\n{timer.ElapsedMilliseconds}ms");
Console.WriteLine($"\n{pi.Count} items");
}
static List<Task> ExecuteRequests()
{
var tasks = new List<Task>();
for (int i = 0; i < TotalRequests; i++)
{
ThreadPool.GetAvailableThreads(out int workerThreads, out int completionPortThreads);
while (workerThreads < 1)
{
ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
Thread.Sleep(100);
}
tasks.Add(Task.Run(async () =>
{
var currentRequestId = 0;
lock (lockState)
currentRequestId = requestsCount++;
var httpClient = new HttpClient();
var result = await httpClient.GetAsync($"https://api.pi.delivery/v1/pi?start={currentRequestId * 1000}&numberOfDigits=1000");
if (result.StatusCode == System.Net.HttpStatusCode.OK)
{
var json = await result.Content.ReadAsStringAsync();
var content = JsonSerializer.Deserialize<JsonObject>(json)!["content"]!.ToString();
//var content = (await JsonSerializer.DeserializeAsync<JsonObject>(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json)!)!)!)!["content"]!.ToString();
pi.Add(currentRequestId, content);
}
}));
}
return tasks;
}
There`s only one problem - you turned only one part of code, which have problem with threads:
lock (lockState)
currentRequestId = requestsCount++;
But, there`s another one:
pi.Add(currentRequestId, content);
The problem related to dictionary idea - a lot of readers and only one writer. So, you saw case with exception and if you write try catch, you will see AggregateException, which almost in every case mean thread issues, so, you need to do this:
lock (lockState)
pi.Add(currentRequestId, content);
I put a lock statement around the dicitionary manipulation as #AlexeiLevenkov mentioned and it worked fine.
tasks.Add(Task.Run(async () =>
{
var currentRequestId = 0;
lock (lockState)
currentRequestId = requestsCount++;
var httpClient = new HttpClient();
var result = await httpClient.GetAsync($"https://api.pi.delivery/v1/pi?start={currentRequestId * 1000}&numberOfDigits=1000");
if (result.StatusCode == System.Net.HttpStatusCode.OK)
{
var json = await result.Content.ReadAsStringAsync();
var content = JsonSerializer.Deserialize<JsonObject>(json)!["content"]!.ToString();
//var content = (await JsonSerializer.DeserializeAsync<JsonObject>(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json)!)!)!)!["content"]!.ToString();
lock (lockState)
pi.Add(currentRequestId, content);
}
}));
I'm not directly answering the question, just suggesting that you can use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:
static void Main(string[] args)
{
var timer = new Stopwatch();
timer.Start();
(int currentRequestId, string content)[] results = ExecuteRequests(50).ToArray().Wait()
timer.Stop();
foreach (var item in results.OrderBy(x => x.currentRequestId))
Console.Write(item.content);
Console.WriteLine($"\n\n{timer.ElapsedMilliseconds}ms");
Console.WriteLine($"\n{results.Count()} items");
}
static IObservable<(int currentRequestId, string content)> ExecuteRequests(int totalRequests) =>
Observable
.Defer(() =>
from currentRequestId in Observable.Range(0, totalRequests)
from content in Observable.Using(() => new HttpClient(), hc =>
from result in Observable.FromAsync(() => hc.GetAsync($"https://api.pi.delivery/v1/pi?start={currentRequestId * 1000}&numberOfDigits=1000"))
where result.StatusCode == System.Net.HttpStatusCode.OK
from json in Observable.FromAsync(() => result.Content.ReadAsStringAsync())
select JsonSerializer.Deserialize<JsonObject>(json)!["content"]!.ToString())
select new
{
currentRequestId,
content,
});
I have a C# console app. In this app, I have a method that I will call DoWorkAsync. For the context of this question, this method looks like this:
private async Task<string> DoWorkAsync()
{
System.Threading.Thread.Sleep(5000);
var random = new Random();
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var length = random.Next(10, 101);
await Task.CompletedTask;
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
I call DoWorkAsync from another method that determines a) how many times this will get ran and b) if each call will be ran in parallel or sequentially. That method looks like this:
private async Task<Task<string>[]> DoWork(int iterations, bool runInParallel)
{
var tasks = new List<Task<string>>();
for (var i=0; i<iterations; i++)
{
if (runInParallel)
{
var task = Task.Run(() => DoWorkAsync());
tasks.Add(task);
}
else
{
await DoWorkAsync();
}
}
return tasks.ToArray();
}
After all of the tasks are completed, I want to display the results. To do this, I have code that looks like this:
var random = new Random();
var tasks = await DoWork(random.Next(10, 101);
Task.WaitAll(tasks);
foreach (var task in tasks)
{
Console.WriteLine(task.Result);
}
This code works as expected if the code runs in parallel (i.e. runInParallel is true). However, when runInParallel is false (i.e. I want to run the Tasks sequentially) the Task array doesn't get populated. So, the caller doesn't have any results to work with. I don't know how to fix it though. I'm not sure how to add the method call as a Task that will run sequentially. I understand that the idea behind Tasks is to run in parallel. However, I have this need to toggle between parallel and sequential.
Thank you!
the Task array doesn't get populated.
So populate it:
else
{
var task = DoWorkAsync();
tasks.Add(task);
await task;
}
P.S.
Also your DoWorkAsync looks kinda wrong to me, why Thread.Sleep and not await Task.Delay (it is more correct way to simulate asynchronous execution, also you won't need await Task.CompletedTask this way). And if you expect DoWorkAsync to be CPU bound just make it like:
private Task<string> DoWorkAsync()
{
return Task.Run(() =>
{
// your cpu bound work
return "string";
});
}
After that you can do something like this (for both async/cpu bound work):
private async Task<string[]> DoWork(int iterations, bool runInParallel)
{
if(runInParallel)
{
var tasks = Enumerable.Range(0, iterations)
.Select(i => DoWorkAsync());
return await Task.WhenAll(tasks);
}
else
{
var result = new string[iterations];
for (var i = 0; i < iterations; i++)
{
result[i] = await DoWorkAsync();
}
return result;
}
}
Why is DoWorkAsync an async method?
It isn't currently doing anything asynchronous.
It seems that you are trying to utilise multiple threads to improve the performance of expensive CPU-bound work, so you would be better to make use of Parallel.For, which is designed for this purpose:
private string DoWork()
{
System.Threading.Thread.Sleep(5000);
var random = new Random();
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var length = random.Next(10, 101);
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
private string[] DoWork(int iterations, bool runInParallel)
{
var results = new string[iterations];
if (runInParallel)
{
Parallel.For(0, iterations - 1, i => results[i] = DoWork());
}
else
{
for (int i = 0; i < iterations; i++) results[i] = DoWork();
}
return results;
}
Then:
var random = new Random();
var serial = DoWork(random.Next(10, 101));
var parallel = DoWork(random.Next(10, 101), true);
I think you'd be better off doing the following:
Create a function that creates a (cold) list of tasks (or an array Task<string>[] for instance). No need to run them. Let's call this GetTasks()
var jobs = GetTasks();
Then, if you want to run them "sequentially", just do
var results = new List<string>();
foreach (var job in jobs)
{
var result = await job;
results.Add(result);
}
return results;
If you want to run them in parallel :
foreach (var job in jobs)
{
job.Start();
}
await results = Task.WhenAll(jobs);
Another note,
All this in itself should be a Task<string[]>, the Task<Task<... smells like a problem.
I have a method which I call in a new task with
// get the dispatcher for the UI thread
var uiDispatcher = Dispatcher.CurrentDispatcher;
Task.Factory.StartNew(() => BackgroundThreadProc(uiDispatcher));
In the method BackgroundThreadProc() I need a delay of few seconds. I tried it with the DispatcherTimer and the task.delay function but it didn't work. The only thing which worked was the System.Threading.Thread.Sleep(1) but I think the Thread.Sleep() function isn't the best solution.
This is my function:
public void BackgroundThreadProc(Dispatcher uiDispatcher)
{
for (var i = 0; i < 100; i++)
{
var task = Task.Delay(1000).ContinueWith(t =>
{
// create object
var animal = new Animal { Name = "test" + i };
uiDispatcher.Invoke(new Action(() => log(animal)));
});
}
}
As I found out it didn't work because the DispatcherTimer is running in the UI thread. How I can accomplish the delay in the function which is in a other thread than the UI thread?
Update:
Now I tried it with the timer:
public void BackgroundThreadProc(Dispatcher uiDispatcher)
{
for (var i = 0; i < 100; i++)
{
var _delayTimer = new System.Timers.Timer();
_delayTimer.Interval = 1000;
//_delayTimer.Enabled = true;
_delayTimer.Elapsed += delegate
{
var animal = new Animal { Name = "test" + i };
uiDispatcher.Invoke(new Action(() => log(animal)));
_delayTimer.Stop();
};
_delayTimer.Start();
}
}
Use Task.Delay to introduce a delay asynchrnoously:
var task = Task.Delay(1000)
.ContinueWith(t => BackgroundThreadProc());
Are you limited to C# 4.0? I assume you're not, because Task.Delay wouldn't be available.
So, make BackgroundThreadProc an async method and use await inside it:
// get the dispatcher for the UI thread
var uiDispatcher = Dispatcher.CurrentDispatcher;
var task = BackgroundThreadProc(uiDispatcher));
// ...
public async Task BackgroundThreadProc(Dispatcher uiDispatcher)
{
for (var i = 0; i < 100; i++)
{
await Task.Delay(1000).ConfigureAwait(false);
// create object
var animal = new Animal { Name = "test" + i };
uiDispatcher.Invoke(new Action(() => log(animal)));
}
}
You really don't need Task.Factory.StartNew here, the execution will continue on thread pool after await Task.Delay.
Apparently, you're only updating the UI from this BackgroundThreadProc. If that's the case, just remove ConfigureAwait(false) and don't use uiDispatcher.Invoke:
public async Task BackgroundThreadProc()
{
for (var i = 0; i < 100; i++)
{
await Task.Delay(1000);
// create object
var animal = new Animal { Name = "test" + i };
log(animal);
}
}
This loop will be executing asynchronously on the WPF UI thread.
Otherwise, if you do have any other CPU-bound work before Task.Delay, then you may need Task.Factory.StartNew to avoid freezing the UI (note Unwrap):
var task = Task.Factory.StartNew(() =>
BackgroundThreadProc(uiDispatcher)).Unwrap();
You can also use Task.Run, which unwraps the inner task automatically:
var task = Task.Run(() => BackgroundThreadProc(uiDispatcher));
Task<string>[] tableOfWebClientTasks = new Task<string>[taskCount];
for (int i = 0; i < taskCount; i++)
{
tableOfWebClientTasks[i] = new WebClient().DownloadStringTask(allUrls[count - i - 1]);
}
Task.Factory.ContinueWhenAll(tableOfWebClientTasks, tasks =>
{
Parallel.ForEach(tasks, task =>
{
//Here I have result from each task.
//But information which url is executed on this task, is lost.
});
});
I could, for example, create class (with two public property, one for task, and second for url) and return instance. But This method i connected with others methods.
Have you some solution for this issue?
If you want to be able to associate your tasks with the url that created them you could use a dictionary to do the mapping:
Task<string>[] tableOfWebClientTasks = new Task<string>[taskCount];
var taskIdToUrl = new Dictionary<int,string>();
for (int i = 0; i < taskCount; i++)
{
var url = allUrls[count - i - 1];
var task = new WebClient().DownloadStringTask(url);
tableOfWebClientTasks[i] = task;
taskIdToUrl.Add(task.Id, url);
}
TaskFactory.ContinueWhenAll(tableOfWebClientTasks, tasks =>
{
Parallel.ForEach(tasks, task =>
{
// To get the url just do:
var url = taskIdToUrl[task.Id];
});
});
I helped create a background task system for an ASP.NET web site.
This is my root Task
Task.Factory.RunNew(RunTimer);
This is called from the root Task.
private void RunTimer()
{
while (!cancellationToken.IsCancellationRequested)
{
var backgroundTasks = _tasks.Values.ToArray();
var tplTasks = new List<Task>();
foreach (var backgroundTask in backgroundTasks)
{
var newTask = new Task(() => backgroundTask.Run());
tplTasks.Add(newTask);
newTask.Start();
}
Task.WaitAll(tplTasks.ToArray());
for (int i = 0; i < NumberOfSecondsToWait &&
!cancellationToken.IsCancellationRequested; i++)
{
Thread.Sleep(new TimeSpan(0, 0, 1));
}
}
}
_tasks is a ConcurrentDictionary<string, IBackgroundTask>. For what ever reason, newTask is executed 2 times on separate threads -- namely backgroundTask.Run() is called twice. RunTimer is only called once. NumberOfSecondsToWait is 60. I've verified that tplTasks only has 2 items in it.
Anyone have any idea?
This is because lambdas (in particular, the newTask lambda) bind to variables, not values.
You need:
...
foreach (var backgroundTask in backgroundTasks)
{
var localBackgroundTask = backgroundTask;
var newTask = new Task(() => localBackgroundTask.Run());
...
}
...