I've made a real simple console app that I am using to break down a problem I am having on a larger application. I feel like I must have missed something in thread 101 class but I'm at a loss of what it is. Basically, the whole premise is to have a BackgroundWorker
check on a timer a collection and then if another collection doesn't contain something from the first collection, start a new Task. If any anytime the Task isn't in running or created status, I want to cancel the task and create a new one.
The two behaviors I am noticing is one when there is an exception inside of the task, I am getting errors for Car Models that don't have an error (even though the task is new
), and if I use a for loop my i
iteration goes to 5 and the collection never has more than 4. I've also noticed that all 4 make an error at the same time, but I know there are some weird things with Random but I'm just not sure if that's the issue at this point.
If you want to see the for iteration go to 5, just use Y
for the console readline, but this only happens in debug, running the application this doesn't seem to occur. The next thing I want to make sure of is that I am canceling the task properly.
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.ComponentModel;
using System.IO;
namespace ThreadTest
{
class Program
{
private static BackgroundWorker _backgroundWorker;
private static void _EstablishBackgroundWorker()
{
_backgroundWorker = new BackgroundWorker();
if (UseFor)
_backgroundWorker.DoWork += _UpdateCarsFor;
else
_backgroundWorker.DoWork += _UpdateCars;
Timer timer = new Timer(10000);
timer.Elapsed += _Timer_Elapsed;
timer.Start();
}
private static void _Timer_Elapsed(object sender, ElapsedEventArgs e)
{
while (_backgroundWorker.IsBusy)
{
}
if (!_backgroundWorker.IsBusy)
_backgroundWorker.RunWorkerAsync();
}
private static void _UpdateCarsFor(object sender, DoWorkEventArgs e)
{
string[] myCars = File.ReadAllLines(@"C:\cars\mycarfile.txt");
for (int i = 0; i < myCars.Length; i++)
if (!_cars.Where(x => x.Model == myCars[i].ToUpper()).Any())
_cars.Add(new Model.Car() { Model = myCars[i].ToUpper() });
for (int i = 0; i < _cars.Count; i++)
{
if (_cars[i].Task != null && _cars[i].Task.Status != TaskStatus.Running && _cars[i].Task.Status != TaskStatus.Created)
{
Console.WriteLine($"Making new task for { _cars[i].Model }");
_cars[i].tokenSource.Cancel();
_cars[i].RefreshToken();
_cars[i].Task = null;
}
if (_cars[i].Task == null)
{
_cars[i].Task = new Task(() => new Manufacture.Build().MakeCar(_cars[i].Model), _cars[i].cancellationToken);
_cars[i].Task.Start();
}
}
}
private static void _UpdateCars(object sender, DoWorkEventArgs e)
{
//string[] myCars = File.ReadAllLines(@"C:\cars\mycarfile.txt");
string[] myCars = new string[] { "F150", "RAM", "M3", "NSX" };
for (int i = 0; i < myCars.Length; i++)
if (!_cars.Where(x => x.Model == myCars[i].ToUpper()).Any())
_cars.Add(new Model.Car() { Model = myCars[i].ToUpper() });
foreach (var car in _cars)
{
if (car.Task != null && car.Task.Status != TaskStatus.Running && car.Task.Status != TaskStatus.Created)
{
Console.WriteLine($"Making new task for { car.Model }");
car.tokenSource.Cancel();
car.RefreshToken();
car.Task = null;
}
if (car.Task == null)
{
car.Task = new Task(() => new Manufacture.Build().MakeCar(car.Model), car.cancellationToken);
car.Task.Start();
}
}
}
private static List<Model.Car> _cars { get; set; }
private static bool UseFor { get; set; }
static void Main(string[] args)
{
_cars = new List<Model.Car>();
Console.WriteLine("Use for iteration? y/n");
var result = Console.ReadLine();
if (result.ToUpper() == "Y")
UseFor = true;
_EstablishBackgroundWorker();
Console.ReadLine();
}
}
}
Car.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadTest.Model
{
class Car
{
public Car()
{
RefreshToken();
}
public string Model { get; set; }
public Task Task { get; set; }
public CancellationToken cancellationToken { get; set; }
public CancellationTokenSource tokenSource { get; set; }
public void RefreshToken()
{
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
}
}
}
Build.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadTest.Manufacture
{
class Build
{
public void MakeCar(string car)
{
try
{
while (true)
{
Random r = new Random();
int chaos = r.Next(0, 100);
Console.WriteLine($"Building {car}");
Thread.Sleep(2000);
if (chaos >= 90) throw new Exception($"Something went wrong with {car}");
}
}
catch(Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
throw;
}
}
}
}
Here is an example of all threads erroring at the same time, but even if i turn random down to a 1% chance this still happens. All errors
In the comments with Henk, the mention of closing over the loop variable I don't believe applies because this method present a foreach and the issue of all threads erroring at the same time occurs, and the question of property canceling a task still remains.
private static void _UpdateCars(object sender, DoWorkEventArgs e)
{
//string[] myCars = File.ReadAllLines(@"C:\cars\mycarfile.txt");
string[] myCars = new string[] { "F150", "RAM", "M3", "NSX" };
for (int i = 0; i < myCars.Length; i++)
if (!_cars.Where(x => x.Model == myCars[i].ToUpper()).Any())
_cars.Add(new Model.Car() { Model = myCars[i].ToUpper() });
foreach (var car in _cars)
{
if (car.Task != null && car.Task.Status != TaskStatus.Running && car.Task.Status != TaskStatus.Created)
{
Console.WriteLine($"Making new task for { car.Model }");
car.tokenSource.Cancel();
car.RefreshToken();
car.Task = null;
}
if (car.Task == null)
{
car.Task = new Task(() => new Manufacture.Build().MakeCar(car.Model), car.cancellationToken);
car.Task.Start();
}
}
}