Passing an instance method (of a struct) to ThreadStart seems to update a spurious instance as the original is unaffected
My problem is passing the this.folderFolder
instance method to ThreadStart
ctor. I step through it with dirAssThread
and watch it update the instance data member correctly and complete, then I trap back to
if (dirAssThread.IsAlive) completeThread(-1); //***ie abort
and find that the data member of the same this
instance that I passed with the method to the ThreadStart
ctor has miraculously reset itself to 0!
Here are the other functions
using System;
using System.IO;
using System.Threading;
namespace MonitorService
{
struct myStruct
{开发者_Python百科
long bytesSzMember;
Thread dirAssThread;
private Object thisLock;
private void completeThread(long bytesSzNew)
{
lock (thisLock)
{
if (bytesSzNew == -1)
{
dirAssThread.Abort();
Console.WriteLine("A thread timed out.");
}
else
{
bytesSzMember = bytesSzNew;
Console.WriteLine("A thread update size.");
}
}
}
private void folderFolder()
{
long bytesSzNew = 0;
DirectoryInfo di = new DirectoryInfo("C:\\SomeDir");
DirectoryInfo[] directories = di.GetDirectories("*",SearchOption.AllDirectories);
FileInfo[] files = di.GetFiles("*",SearchOption.AllDirectories);
foreach (FileInfo file in files)
{
bytesSzNew += file.Length;
}
completeThread(bytesSzNew);
}
private void updateSize()
{
thisLock = new Object();
dirAssThread = new Thread(new ThreadStart(this.folderFolder));
dirAssThread.Start();
Thread.Sleep(5000);
if (dirAssThread.IsAlive) completeThread(-1);
}
}
}
Update
After the question title update, the problem you are seeing is that structs are copied on reference. You are passing a copy of your struct when assigning the delegate to the thread, and it is this copy that will be updated by the thread. When you do your check in completeThread
it is against the original which has not been updated.
Use a class instead of a struct.
Alternate Solution
I would suggest using wait handles instead of sleeps and thread aborts, as Thread.Abort
is considered a dangerous practice and should be avoided (quite easily in this case). I propose the following solution which is a recursive version that will not follow circular references (so there is no need to abort in reality, the code can be removed if you do not want a timeout facility).
public class WaitForFileSizes
{
private readonly object _syncObj = new object();
private readonly HashSet<string> _seenDirectories = new HashSet<string>();
private readonly ManualResetEvent _pEvent = new ManualResetEvent(false);
private long _totalFileSize;
private Thread _thread;
private volatile bool _abort;
private void UpdateSize()
{
_thread = new Thread(GetDirectoryFileSize);
_thread.Start();
bool timedout = !_pEvent.WaitOne(5000);
if (timedout)
{
_abort = true;
_pEvent.WaitOne();
Console.WriteLine("A thread timed out.");
}
else
{
Console.WriteLine("Total size {0}b.", _totalFileSize);
}
}
private void GetDirectoryFileSize()
{
GetDirectoryFileSizesRecursively(new DirectoryInfo("C:\\temp"));
_pEvent.Set();
}
private void GetDirectoryFileSizesRecursively(DirectoryInfo dir)
{
Parallel.ForEach(dir.EnumerateFiles(), f =>
{
if (_abort)
{
_pEvent.Set();
return;
}
Interlocked.Add(ref _totalFileSize, f.Length);
});
Parallel.ForEach(dir.EnumerateDirectories(), d =>
{
if (!IsSeen(d))
{
GetDirectoryFileSizesRecursively(d);
}
});
}
private bool IsSeen(DirectoryInfo dir)
{
lock (_syncObj)
{
if (!_seenDirectories.Contains(dir.FullName))
{
_seenDirectories.Add(dir.FullName);
return false;
}
return true;
}
}
}
Update
As we now have circular reference detection, the threading and abort code can be removed as that was previously there to abort the thread if it was in an endless loop - no need for that now:
public class WaitForFileSizes
{
private readonly object _syncObj = new object();
private readonly HashSet<string> _seenDirectories = new HashSet<string>();
private long _t;
public void UpdateSize()
{
GetSize(new DirectoryInfo("C:\\temp"));
Console.WriteLine("Total size {0}b.", _t);
}
private void GetSize(DirectoryInfo dir)
{
Parallel
.ForEach(dir.EnumerateFiles(), f => Interlocked.Add(ref _t, f.Length));
Parallel
.ForEach(dir.EnumerateDirectories().Where(IsNewDir), GetSize);
}
private bool IsNewDir(FileSystemInfo dir)
{
lock (_syncObj)
{
if (!_seenDirectories.Contains(dir.FullName))
{
_seenDirectories.Add(dir.FullName);
return true;
}
return false;
}
}
}
The issue you are having here is that you are passing a method of a struct
to the ThreadStart
constructor and that causes it to make a copy of the struct
instance and invoke the method on the copy. Your code is running, but it is updating the copy not the original instance.
Try changing the struct
to class
and you should see the problem go away.
This is one of the nuances of value (struct) types. There are very few reasons to use a value type - and in a wide variety of cases they add overhead (instead of eliminating it, like you would assume).
Just change the type declaration to class
. Furthermore, if you want a timeout for this function, why not try the following (you can eliminate the class altogether):
static void Main(string[] args)
{
CalculateSize("C:\\", 1000,
result => Console.WriteLine("Finished: {0} bytes", result),
(result, ex) => Console.WriteLine("Incomplete results: {0} bytes - {1}", result, ex.Message));
Console.ReadLine();
}
public static void CalculateSize(string directory, int timeout, Action<long> onSuccess, Action<long, Exception> onFailure)
{
// Create an invoke a delegate on a separate thread.
var del = new Action<string, int, Action<long>, Action<long, Exception>>(CalculateSizeImpl);
del.BeginInvoke(directory, timeout, onSuccess, onFailure, iar =>
{
try
{
del.EndInvoke(iar);
}
catch (Exception ex)
{
onFailure(0, ex);
}
}, null);
}
static void CalculateSizeImpl(string directory, int timeout, Action<long> onSuccess, Action<long, Exception> onFailure)
{
var completeBy = Environment.TickCount + timeout;
var size = 0L;
var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
try
{
CalculateSizeRecursive(directory, completeBy, ref size, visited);
}
catch (Exception ex)
{
// Call the failure callback, but give the
// value before the timeout to it.
onFailure(size, ex);
return;
}
// Just return the value.
onSuccess(size);
}
static void CalculateSizeRecursive(string directory, int completeBy, ref long size, HashSet<string> visited)
{
foreach (var file in Directory.GetFiles(directory, "*"))
{
if (Environment.TickCount > completeBy)
throw new TimeoutException();
size += new FileInfo(file).Length;
}
// Cannot use SearchOption.All, because you won't get incomplete results -
// only ever 0 or the actual value.
foreach (var dir in Directory.GetDirectories(directory, "*"))
{
if (Environment.TickCount > completeBy)
throw new TimeoutException();
if (visited.Add(dir))
CalculateSizeRecursive(dir, completeBy, ref size, visited);
}
}
精彩评论