将实例方法(结构)传递给ThreadStart似乎更新了一个虚假的实例,因为原始实例不受影响
本文关键字:实例 一个 不受影响 原始 因为 更新 结构 实例方法 ThreadStart | 更新日期: 2023-09-27 18:07:33
我的问题是将this.folderFolder
实例方法传递给ThreadStart
实例。我用dirAssThread
逐步完成它,并观察它正确更新实例数据成员并完成,然后我返回
if (dirAssThread.IsAlive) completeThread(-1); //***ie abort
并发现我用方法传递给ThreadStart
的同一个this
实例的数据成员奇迹般地将自己重置为0!
下面是其他函数
using System;
using System.IO;
using System.Threading;
namespace MonitorService
{
struct myStruct
{
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);
}
}
}
更新
在问题标题更新之后,您看到的问题是结构体在引用时被复制。当将委托分配给线程时,传递的是struct的副本,而这个副本将由线程更新。当你在completeThread
中检查时,它是针对未更新的原件的。
使用类而不是结构体。
替代解决方案
我建议使用等待句柄而不是睡眠和线程中止,因为Thread.Abort
被认为是一个危险的做法,应该避免(在这种情况下很容易)。我提出了以下解决方案,这是一个不遵循循环引用的递归版本(因此实际上不需要中止,如果您不想要超时功能,可以删除代码)。
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;
}
}
}
因为我们现在有了循环引用检测,线程和中止代码可以被删除,因为如果线程处于无限循环中,那么线程和中止代码就可以被删除——现在不需要了:
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;
}
}
}
您在这里遇到的问题是,您正在将struct
的方法传递给ThreadStart
构造函数,这导致它复制struct
实例并在副本上调用该方法。您的代码正在运行,但它正在更新副本而不是原始实例。
尝试将struct
更改为class
,您应该会看到问题消失。
这是值(结构)类型的一个细微差别。使用值类型的原因很少——而且在很多情况下,它们会增加开销(而不是像您想象的那样消除开销)。
只需将类型声明更改为class
。此外,如果您想要这个函数的超时,为什么不试试下面的方法(您可以完全消除这个类):
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);
}
}