I've been writing an AI that plays chess recently. The AI is designed to run two separate instances, each connected to a client server. The server calls a run function for each AI in turn. What I'm trying to do is write code that ponders while the other AI is making its move. However, I've come across an issue. I'll show the code so as to make explaining said issue easier:
public override bool run()
{
PonderSearch ponderSearch = new PonderSearch();
Thread ponderThread;
AIMove bestMove = null;
int Depth = 0;
// Print the board
if (moves.Length < 2)
{
theBoard.Print();
}
if (!FirstTurn)
{
AIMove lastMove = new AIMove(AI.moves[0]);
Depth = ponderSearch.Depth;
foreach (MoveTable result in ponderSearch.TheTable)
{
if (result.TheMove == lastMove)
{
bestMove = result.TheResult.Move;
}
}
// End thread
ponderThread.Abort();
ponderThread.Join();
}
// Looks through information about the players
for (int p = 0; p < players.Length; p++)
{
Console.Write(players[p].getPlayerName());
// if playerID is 0, you're white, if its 1, you're black
if (players[p].getId() == playerID())
{
Console.Write(" (ME)");
}
Console.WriteLine(" time remaining: " + players[p].getTime());
}
AIMove otherPMove = new AIMove();
AIPiece pieceMoved = new AIPiece();
// if there has been a move, print the most recent move
if (moves.Length > 0)
{
// Update the board state with previous move
theBoard = theBoard.Update();
pieceMoved = theBoard.GetPiece((short)moves[0].getToRank(),
(short)moves[0].getToFile());
otherPMove = new AIMove(moves[0], pieceMoved);
if (lastMoves.Count >= 8)
{
lastMoves.RemoveAt(7);
}
lastMoves.Insert(0, otherPMove);
}
// Generate move
Search theSearch = new Search(lastMoves);
if (!FirstTurn)
{
theSearch.Update(Depth, bestMove);
}
AIMove theMove = theSearch.Minimax(theBoard, (short)playerID());
// Update last 8 moves
if (lastMoves.Count >= 8)
{
lastMoves.RemoveAt(7);
}
lastMoves.Insert(0, theMove);
if (theMove != null)
{
Console.WriteLine("Move Chosen:");
theMove.Print();
theBoard = theBoard.Move(theMove, (short)playerID());
}
else
{
Console.WriteLine("No move available");
}
theBoard.Print();
// Begin pondering
ponderSearch = new PonderSearch(lastMoves, (short)playerID(), theBoard, theMove);
ponderThread = new Thread(new ThreadStart(ponderSearch.Ponder));
ponderThread.Start();
FirstTurn = false;
return true;
}
Anyway, as written, the compiler throws multiple errors saying my Thread hasn't been initialized but the point is that the function runs multiple times, ending the thread that was started in the most recent call at the beginning of the current one.
Is there any way I can do this?
Thanks,
EDIT: The error I get is:
Error 4 Use of unassigned local variable 'ponderThread' C:\Users\...\AI.CS 52 13 csclient
This has nothing to do with threading. It's a simple scoping issue. All local variables (declared inside a method) is typically put on the stack and cleaned up when the method exists.
Hence the ponderThread will be garbage collected after the run() method have exited. So the next time your method enter it will have the member variable FirstTurn set to true while ponderThread is uninitialized as it's a local variable.
A quick fix is to change the ponderThread variable to a class variable (called member variable in C#).
That will however give you thread synchronization problems as you are going to share state between two threads.
I suggest that you read up a bit more about threads before going further.
Related
my code is a bit complex, but the core is starting threads like this:
Thread task = new Thread(new ParameterizedThreadStart(x => { ThreadReturn = BuildChildNodes(x); }));
task.Start((NodeParameters)tasks[0]);
it should work. but when i check my CPU usage i get barely 10%. so i do assume it's just using one core. barely.
ThreadReturn btw is a value i use a setter on, to have some kind of event, when the thread is ready:
public object ThreadReturn
{
set
{
lock (thisLock)
{
NodeReturn result = (NodeReturn)value;
if (result.states.Count == 0) return;
Content[result.level + 1].AddRange(result.states);
if (result.level + 1 >= MaxDepth) return;
for (int i = 0; i < result.states.Count; i++)
{
Thread newTask = new Thread(new ParameterizedThreadStart(x => ThreadReturn = BuildChildNodes(x)));
NodeParameters param = new NodeParameters()
{
level = result.level + 1,
node = Content[result.level + 1].Count - (i + 1),
turn = SkipOpponent ? StartTurn : !result.turn
};
if (tasks.Count > 100)
unstarted.Add(param);
else
{
newTask.Start(param);
tasks.Add(newTask);
}
}
}
}
}
i got some crazy error about mark stack overflow so i limited the maximum number of parallel tasks with putting them into a second list...
i'm not firm in multithreading so this code is a little bit messy... maybe you can show me a better way which actually uses my cores.
btw: it's not the locks fault. i tried without before. -> same result
Edit: this is my code before i went to the Threading class. i find it more suitable:
Content.Clear();
Content.Add(new List<T> { Root });
for (var i = 0; i < maxDepth; i++)
Content.Add(new List<T>());
Task<object> firstTask = new Task<object>(x => BuildChildNodes(x), (new NodeParameters() { level = 0, node = 0, turn = Turn }));
firstTask.Start();
tasks.Add(firstTask);
while (tasks.Count > 0 && Content.Last().Count == 0)
{
Task.WaitAny(tasks.ToArray());
for (int task = tasks.Count - 1; task >= 0; task--)
{
if (tasks[task].IsCompleted)
{
NodeReturn result = (NodeReturn)tasks[task].Result;
tasks.RemoveAt(task);
Content[result.level + 1].AddRange(result.states);
if (result.level + 1 >= maxDepth) continue;
for (int i = 0; i < result.states.Count; i++)
{
Task<object> newTask = new Task<object>(x => BuildChildNodes(x), (object)new NodeParameters() { level = result.level + 1, node = Content[result.level + 1].Count - (i + 1), turn = SkipOpponent ? Turn : !result.turn });
newTask.Start();
}
}
}
}
In every state i'm calculating children and in my main thread i put them into my state tree while waiting for the tasks to finish. please assume i'd actually use the return value of waitany, i did a git reset and now... welll... it's gone^^
Edit:
Okay i don't know what exactly i did wrong but... in general everything was a total mess. i now implemented the deep construction method and maybe because there's much less... "traffic" now my whole code runs in 200ms. so... thanks for this!
i don't know if i should delete this question hence stupidity or if you guys want to post answers so i can rate them postive, you really helped me a lot :)
Disregarding all the other issues you have here, essentially your lock ruins the show.
What you are saying is, hey random person go and do some stuff! just make sure you don't do it at the same time as anyone else (lock), you could have 1000 threads, but only one thread is going to be active at one time on one core, hence your results.
Here are some other thoughts.
Get the gunk out of the setter, this would fail any sane code review.
Use Tasks instead of Thread.
Thinking about what needs thread safety, and elegantly lock only what needs it, Take a look at the Interlocked for dealing with numeric atomic manipulation
Take a look at the concurrent collections you may get more mileage out of this
Simplify your code.
I can't give any more advice as it's just about impossible to know what you are trying to do.
We've been getting this exception intermittently in our applications:
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Runtime.Remoting.Channels.ChannelServices.get_RegisteredChannels()
at ProductCacheServiceClient.getProductCacheServiceProvider(DataServiceConnectionDetails connOptions)
Since it's intermittent and we call this quite a lot (in a web app, so can be concurrent) I thought maybe it was a threading issue but I've had a look in ChannelServices.get_RegisteredChannels and although this is interacting with a list of channels, it takes a copy of the reference and both RegisterChannel and UnregisterChannel appear to have locks, and replaced the list in one go.
We're stumped... If the locks are working as advertised and the MSDN docs are correct (it says static members of this class are threadsafe) then what could cause an IndexOutOfRange inside this property?
Edit: I've managed to make a small console app that will intermittently (about 1 in 5 runs in my testing) throw this exception. Im compiling in VS 2015 Update 1 targeting .NET 4.5.2.
List<Task> tasks = new List<Task>();
for (var i = 0; i < 1000; i++)
{
var task = Task.Run(() =>
{
var chanProps = new System.Collections.Hashtable();
chanProps["name"] = "cacheServicesClient" + Guid.NewGuid().ToString();
chanProps["username"] = "a";
chanProps["password"] = "b";
chanProps["domain"] = "c";
var channelToReturn = new TcpChannel(chanProps, null, null);
bool registered = false;
foreach (var channelRegistered in ChannelServices.RegisteredChannels)
{
if (channelRegistered.ChannelName == channelToReturn.ChannelName)
{
registered = true;
break;
}
}
if (!registered)
{
ChannelServices.RegisterChannel(channelToReturn, true);
}
}).ContinueWith(t =>
{
if (t.IsFaulted && t.Exception != null)
{
t.Exception.Handle(e =>
{
Console.WriteLine(e.ToString());
return true;
});
}
});
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
and here's the error when it crashes:
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Runtime.Remoting.Channels.ChannelServices.get_RegisteredChannels()
at ConsoleApplication1.Program.<>c.<Main>b__0_0() in C:\Work\Source\TestApps\ConsoleApplication1\Program.cs:line 28
at System.Threading.Tasks.Task.Execute()
Press any key to continue . . .
We've confirmed in our repro that adding locks around this code fixed the problem. We believe the issue occurs when the first thread is inside ChannelServices.RegisterChannel and a second thread hits ChannelServices.RegisteredChannels at the same time. We believe it only happens on the very first hits to these (when the list is first populated with a CrossAppDomainChannel) so possible workarounds include:
Registering a single channel (to completion!) before allowing any other threads to try and create channels
Put locks around the whole chunk of code
Since our use is low volume, it's probably going to be simpler to just lock around the whole thing. Depending on your code, this may or may not be suitable!
In the C# code below, assume that sample is, say, 20000.
Then the line which calls Convert.ToInt16 will throw an overflow exception as it tries to convert 20000 * 3.2 (which is greater than 32768).
In that exception, it sets a bool flag to true (dataErrorWritten), which is supposed to stop the error message being written more than once.
However what I am seeing is some cases where the error message is continually written every time and it appears the dataErrorWritten flag is not doing anything. How can this be possible? Once the exception is caught the first time, dataErrorWritten[i] will be set to true, and when it comes around the next time it should not print any error. In fact, this works 99% of the time, however under certain strange circumstances it does not.
ProcessInData is running on its own thread within a static class.
Any ideas would be greatly appreciated. Thank you.
I have already tracked down one multi-threading bug in this program (shared data queue without lock), and I guess that this might have something to do with multi-threading but I can't see how.
private static bool[] dataErrorWritten;
private static void ProcessInData()
{
short[][] bufferedData = new short[800][];
short sample;
//initialise this bool array to false
dataErrorWriten = new bool[32];
for (int i = 0; i < 32; i++)
{
dataErrorWriten[i] = false;
}
//create buffered data arrays
for (int i = 0; i < bufferedData.Length; i++)
{
bufferedData[i] = new short[32];
}
//loop always true
while (processData)
{
//... Increment bufferLocation
for (int i = 0; i < 32; i++) {
//Get a sample of data
sample = GetSampleFromSomewhere();
try {
bufferedData[bufferLocation][i] = Convert.ToInt16(((float)sample * 3.2);
dataErrorWritten[i] = false;
}
catch (Exception) {
if (!dataErrorWritten[i]) {
EventLog.WriteToErrorLogFile("Invalid UDP sample value " + sample + " received for channel " + (i + 1).ToString() + ".");
dataErrorWritten[i] = true;
}
// ... set buffered data within bounds
}
}
}
}
Your code sets the flag to false after a successful convert and true in the handler.
If your code had a mix of good and bad converts, the flag will toggle and you get multiple errors.
You could remove the set back to "false" to ensure each error is only printed once. Perhaps use a counter instead of a bool. Only output the error when the counter is 0. That way you know how many times the error occurred (for debug purposes), but only report it once.
We are running a web farm using .NET. Each web server holds a considerable amount of static objects in it's memory. A Gen 2 garbage collection (GC) takes 10-20 seconds and it runs every 5 minutes.
We ran more or less into the same problems that StackOverflow ran into: http://samsaffron.com/archive/2011/10/28/in-managed-code-we-trust-our-recent-battles-with-the-net-garbage-collector
At the moment, we are reducing the number of objects in the cache. However, this takes time.
At the same time, we implemented the methods documented here to get notifications in .NET about approaching GCs.
The goal is to take a web server out of the farm when a GC is approaching and include it into the farm after the GC is over.
However, we only get a notification for 0.7% of all GCs.
We are using a maxGenerationThreshold and largeObjectHeapThreshold of 8. We tried other thresholds, but the amount of missed GCs did not change.
We are using concurrent server garbage collection (http://msdn.microsoft.com/en-us/library/ms229357.aspx). The GCLatencyMode is Interactive (see http://msdn.microsoft.com/en-us/library/system.runtime.gclatencymode.aspx). Here again, we tried to use other GC modes (Workstation mode, Batch, etc.). And again we did not get a notification for most of the GCs.
Are we doing something wrong, or is it impossible to get a notification for every GC that occurs?
How can we increase the number of notifications?
According to http://assets.red-gate.com/community/books/assets/Under_the_Hood_of_.NET_Management.pdf, at the beginning a GC is triggered when Gen2 hits ~10 MB. We have a lot of RAM, so if we could set this threshold manually to a higher level, it would take more time to reach this threshold and in my understanding the probability would increase to get a notification. Is there a way to modify this threshold?
This is the code that registers and listens to the notifications:
GC.RegisterForFullGCNotification(gcThreshold, gcThreshold);
// Start a thread using WaitForFullGCProc.
thWaitForFullGC = new Thread(WaitForFullGCProc);
thWaitForFullGC.Name = "HealthTestGCNotificationListenerThread (Threshold=" + gcThreshold + ")";
thWaitForFullGC.IsBackground = true;
WaitForFullGCProc():
private void WaitForFullGCProc()
{
try
{
while (!gcAbort)
{
// Check for a notification of an approaching collection.
GCNotificationStatus s;
do
{
int timeOut = CheckForMissedGc() > 0 ? 5000 : (10 * 60 * 1000);
s = GC.WaitForFullGCApproach(timeOut);
if (this.GcState == GCState.InducedUnnotified)
{
// Set the GcState back to okay to prevent the message from staying in the ApplicationMonitoring.
this.GcState = GCState.Okay;
}
} while (s == GCNotificationStatus.Timeout);
if (s == GCNotificationStatus.Succeeded)
{
SetGcState(GCState.Approaching, "GC is approaching..");
gcApproachNotificationCount++;
}
else
{
...
}
Stopwatch stopwatch = Stopwatch.StartNew();
s = GC.WaitForFullGCComplete((int)PrewarnTime.TotalMilliseconds);
long elapsed = stopwatch.ElapsedMilliseconds;
if (s == GCNotificationStatus.Timeout)
{
if (this.ForceGCWhenApproaching && !this.IsInGc && !this.IsPeriodicGcApproaching)
{
this.IsInGc = true;
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true);
GC.WaitForPendingFinalizers();
elapsed = stopwatch.ElapsedMilliseconds;
this.IsInGc = false;
}
}
}
gcAbort = false;
}
catch (Exception e)
{
}
}
Note: This is more of a comment but includes a large code sample.
Have you considered trying to get your GC notifications through another way? Jeffrey Richter ( CLR via C#) explains a good way to get notifications,it uses an object and checks its finalizer method in what generation it is.
This is the class: It uses internal objects which are either collected if the supplied generation matches ( See new GenObject(0); for example. ) or resurrected for the next higher generation.
And you just subscribe to it with GCNotification.GCDone += GCDoneHandler;
public static class GCNotification
{
private static Action<Int32> s_gcDone = null; // The event's field
public static event Action<Int32> GCDone
{
add
{
// If there were no registered delegates before, start reporting notifications now
if (s_gcDone == null) { new GenObject(0); new GenObject(1); new GenObject(2); }
s_gcDone += value;
}
remove { s_gcDone -= value; }
}
private sealed class GenObject
{
private Int32 m_generation;
public GenObject(Int32 generation) { m_generation = generation; }
~GenObject()
{ // This is the Finalize method
// If this object is in the generation we want (or higher),
// notify the delegates that a GC just completed
if (GC.GetGeneration(this) >= m_generation)
{
Action<Int32> temp = Volatile.Read(ref s_gcDone);
if (temp != null) temp(m_generation);
}
// Keep reporting notifications if there is at least one delegate registered,
// the AppDomain isn't unloading, and the process isn’t shutting down
if ((s_gcDone != null)
&& !AppDomain.CurrentDomain.IsFinalizingForUnload()
&& !Environment.HasShutdownStarted)
{
// For Gen 0, create a new object; for Gen 2, resurrect the object
// & let the GC call Finalize again the next time Gen 2 is GC'd
if (m_generation == 0) new GenObject(0);
else GC.ReRegisterForFinalize(this);
}
else { /* Let the objects go away */ }
}
}
}
I have a class as following
public class ScheduledUpdater
{
private static readonly object lockingObject = new object();
private static Queue<int> PendingIDs = new Queue<int>();
private static bool UpdateThreadRunning = false;
private static bool IsGetAndSaveScheduledUpdateRunning = false;
private static DataTable ScheduleConfiguration;
private static Thread updateRefTableThread;
private static Thread threadToGetAndSaveScheduledUpdate;
public static void ProcessScheduledUpdates(int ID)
{
//do some stuff
// if ( updateRefTableThread not already running)
// execute updateRefTableThread = new Thread(new ThreadStart(UpdateSchedulingRefTableInThrear));
// execute updateRefTableThread.Start();
//do some stuff
***[1]***
GetAndSaveScheduledUpdate(ID)
}
private static void UpdateSchedulingRefTableInThrear()
{
//if(updateRefTableThread==true)
// return;
// updateRefTableThread = true
do{
UpdateSchedulingRefTable();
Thread.sleep(800000);
}while(updateRefTableThread);
//updateRefTableThread = false;
}
public static void UpdateSchedulingRefTable()
{
// read DB and update ScheduleConfiguration
string query = " SELECT ID,TimeToSendEmail FROM TBLa WHERE MODE = 'WebServiceOrder' AND BDELETE = false ";
clsCommandBuilder commandBuilder = new clsCommandBuilder();
DataSet ds = commandBuilder.GetDataSet(query);
if (ds != null && ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
{
List<string> lstIDs = new List<string>();
for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
{
lstIDs.Add(ds.Tables[0].Rows[i]["ID"].ToString());
if (LastEmailSend.Contains(ds.Tables[0].Rows[i]["ID"].ToString()))
LastEmailSend[ds.Tables[0].Rows[i]["ID"].ToString()] = ds.Tables[0].Rows[i]["TimeToSendEmail"].ToString();
else
LastEmailSend.Add(ds.Tables[0].Rows[i]["ID"].ToString(), ds.Tables[0].Rows[i]["TimeToSendEmail"].ToString());
}
if (lstIDs.Count > 0)
{
string Ids = string.Join(",", lstIDs.ToArray()).Trim(',');
dhDBNames dbNames = new dhDBNames();
dbNames.Default_DB_Name = dbNames.ControlDB;
dhGeneralPurpose dhGeneral = new dhGeneralPurpose();
dhGeneral.StringDH = Ids;
DataSet result = commandBuilder.GetDataSet(dbNames, (object)dhGeneral, "xmlGetConfigurations");
if (result != null && result.Tables.Count > 0)
{
***[2]***
Monitor.Enter(lockingObject);
if (ScheduleConfiguration != null)
ScheduleConfiguration.Clear();
ScheduleConfiguration = result.Tables[0];
Monitor.Exit(lockingObject);
}
}
}
}
public static void GetAndSaveScheduledUpdate(int ID)
{
***[3]***
//use ScheduleConfiguration
Monitor.Enter(lockingObject);
if (ScheduleConfiguration == null)
{
UpdateSchedulingRefTable();
Monitor.Exit(lockingObject);
}
else
Monitor.Exit(lockingObject);
Monitor.Enter(lockingObject);
DataRow[] result = ScheduleConfiguration.Select("ID = "+ID);
Monitor.Exit(lockingObject);
//then for each result row, i add this to a static Queue PendingIDs
}
}
The function UpdateSchedulingRefTable can be called any time from outside world (for instance if someone updates the schedule configuration manually)
ProcessScheduledUpdates is called from a windows service every other minute.
Problem:
Datatable ScheduleConfiguration is updated in the UpdateSchedulingRefTable (called from outside world - say manually) but when i try to use Datatable ScheduleConfiguration in GetAndSaveScheduledUpdate, i get the older version of values....
What am I missing in this stuff???
About EDIT: I thought the stuff i have not shown is quite obvious and possibly not desired, perhaps my structure is wrong :) and sorry for incorrect code previously, i made a simple function call as a thread initialization... sorry for my code indentation too because i don't know how to format whole block...
Edit 2: ProcessScheduledUpdates is called from a windows service (say every second).
I am first hitting point [1] (it will automatically hit point [2] and update ScheduleConfiguration), then i manually update DB and hit point [2]. Then i hit point [1] again to just check the status
My problem is now, that i get two entirely different copies of ScheduleConfiguration at point [1] and [2].
Edit 3 I think my question is invalid; i wanted to share data betweena a windows form click and a windows service. No matter how many shared things i have, atleast the address space will be different... My bad...
Well, for one thing there's no sign that you've got any memory barriers anywhere which would ensure that any writes are fully "flushed" to main memory, and that reads go to main memory instead of using registers etc. Basically you shouldn't use fields shared between threads without some sort of protection.
Now that's a theoretical risk to some extent - it's not clear that that's actually the problem, although it's certainly a real possibility. I strongly suspect there's another problem, but in the code that you haven't shown us. Do you have diagnostics to indicate that the first thread definitely wrote to ScheduleConfiguration?