C# Passed state object to Task gets changed
I'm fighting with this for days, I hope You can push me in the right direction.
This is a recursive threading algorithm which parses a resource in a thread looking for links to other resources storing them in a ConcurrentBag
for future TakeOuts. Threads creation is limited by an array with configurable size to preserve resources.
I have a private static ConcurrentBag<string>
which gets filled by many threads. These are Tasks
that are stored in private static Task[]
with configurable size (app preferences).
Main
that's doing TryTake()
into local string url
variable. When successfull it loops the Task[]
trying to find empty slot creating new Task
passing state object url
and storing it in Task[]
like this:
TaskArray[x] = new Task(FindLinks, url, TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
The FindLinks
is declared as
private static readonly Action<object> FindLinks = input => { ... }
In the main Task[]
loop I am setting url
to null
before next TryTake(out url)
.
What is my problem here is the state object input
that is passed from url
in the main loop becomes null
inside the Task lambda function. I've read almost all MSDN articles about TPL and can't figure this one out :(
How can I pass a variable (string) to the Task
safely without closure (or whatever it is happening).
Any other ideas about improving this algorithm are welcome too.
Thanks.
Edit:
I have solved the problem by reordering statements and slightly rewriting the code in the main loop. No more assigning null to the variable. I suspect it was caused by compiler's statement reordering or preemption. Here is what it looks like now causing no more troubles:string url;
if (CollectedLinks.TryTake(out url))
{
var queued = false;
while (!开发者_JAVA技巧queued)
{
// Loops thru the array looking for empty slot (null)
for (byte i = 0; i < TaskArray.Length; i++)
{
if (TaskArray[i] == null)
{
TaskArray[i] = new Task(FindLinks, url, TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
TaskArray[i].Start(TaskScheduler.Current);
queued = true; break;
}
}
if (!queued)
{
// Loop and clean the array
for (var i = 0; i < TaskArray.Length; i++)
{
if (TaskArray[i] == null)
continue;
if (TaskArray[i].Status == TaskStatus.RanToCompletion || TaskArray[i].Status == TaskStatus.Canceled || TaskArray[i].Status == TaskStatus.Faulted)
{
TaskArray[i].Wait(0);
TaskArray[i] = null;
}
}
}
}
}
I have solved the problem by reordering statements and slightly rewriting the code in the main loop. No more assigning null to the variable. I suspect it was caused by compiler's statement reordering or preemption. Here is what it looks like now causing no more troubles:
string url;
if (CollectedLinks.TryTake(out url))
{
var queued = false;
while (!queued)
{
// Loops thru the array looking for empty slot (null)
for (byte i = 0; i < TaskArray.Length; i++)
{
if (TaskArray[i] == null)
{
TaskArray[i] = new Task(FindLinks, url, TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
TaskArray[i].Start(TaskScheduler.Current);
queued = true; break;
}
}
if (!queued)
{
// Loop and clean the array
for (var i = 0; i < TaskArray.Length; i++)
{
if (TaskArray[i] == null)
continue;
if (TaskArray[i].Status == TaskStatus.RanToCompletion || TaskArray[i].Status == TaskStatus.Canceled || TaskArray[i].Status == TaskStatus.Faulted)
{
TaskArray[i].Wait(0);
TaskArray[i] = null;
}
}
}
}
}
精彩评论