C# 将(结构的)实例方法传递给ThreadStart似乎会更新虚假实例,因为原始实例不受影响
我的问题是将this.folderFolder实例方法传递给ThreadStart。我用dirAssThread一步一步地检查它,看它正确地更新了实例数据成员并完成了更新,然后我返回到C# 将(结构的)实例方法传递给ThreadStart似乎会更新虚假实例,因为原始实例不受影响,c#,multithreading,methods,struct,locking,C#,Multithreading,Methods,Struct,Locking,我的问题是将this.folderFolder实例方法传递给ThreadStart。我用dirAssThread一步一步地检查它,看它正确地更新了实例数据成员并完成了更新,然后我返回到 if (dirAssThread.IsAlive) completeThread(-1); //***ie abort 然后发现我用这个方法传递给ThreadStart的同一个实例的数据成员奇迹般地将自己重置为0 以下是其他功能 using System; using System.IO; using Syst
if (dirAssThread.IsAlive) completeThread(-1); //***ie abort
然后发现我用这个方法传递给ThreadStart的同一个实例的数据成员奇迹般地将自己重置为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);
}
}
}
更新
在问题标题更新之后,您看到的问题是结构是在引用时复制的。将委托分配给线程时,您正在传递结构的副本,线程将更新该副本。当您办理完入住手续后,请阅读未更新的原件
使用类而不是结构
替代溶液
我建议使用等待句柄而不是休眠和线程中止,因为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;
}
}
}
更新
在问题标题更新之后,您看到的问题是结构是在引用时复制的。将委托分配给线程时,您正在传递结构的副本,线程将更新该副本。当您办理完入住手续后,请阅读未更新的原件
使用类而不是结构
替代溶液
我建议使用等待句柄而不是休眠和线程中止,因为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;
}
}
}
这里的问题是,您正在将结构的方法传递给ThreadStart构造函数,这会导致它创建结构实例的副本并在副本上调用该方法。您的代码正在运行,但它正在更新副本,而不是原始实例
尝试将结构更改为类,您会看到问题消失。这里的问题是,您正在将结构的方法传递给ThreadStart构造函数,这会导致它复制结构实例并调用副本上的方法。您的代码正在运行,但它正在更新副本,而不是原始实例
尝试将结构更改为类,您将看到问题消失。这是值结构类型的细微差别之一。使用值类型的理由很少,而且在很多情况下,它们会增加开销,而不是像您假设的那样消除开销 只需将类型声明更改为类。此外,如果您希望此函数超时,为什么不尝试以下操作?您可以完全消除该类:
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);
}
}
这是值结构类型的细微差别之一。使用值类型的理由很少,而且在很多情况下,它们会增加开销,而不是像您假设的那样消除开销 只需将类型声明更改为类。此外,如果您希望此函数超时,为什么不尝试以下操作?您可以完全消除该类:
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);
}
}
现在还不清楚你的代码在做什么-你能发布更多的信息吗,或者最好是一个简短但完整的程序吗?@Henrik,不,为了确保完整性,它建议的更新被转发到completeThread,以确保在更新发生之前它不会到期并被终止。@Henrik,我不得不假设我的意思是, bytesSzCode@chibacity,它将完成,5000ms是添加尺寸的足够时间。它将更新大小。如果它没有完成,它将被抢占,bytesSzNew将不会被复制到byteSzCode。@chibacity我知道如果它不能完成,它将通过-1。其效果将是抢占该线程。这是一个看门狗终止符,用于防止在搜索的目录结构中循环引用。您的代码在做什么并不清楚-您可以发布更多信息,或者理想情况下发布一个简短但完整的程序吗?@Henrik,否,为了确保完整性,它建议的更新被转发到completeThread,以确保其不完整
t到期,并在更新发生之前被终止。@Henrik,在3个中,我想我的意思是,bytesSzCode@chibacity,它将完成,5000ms是添加尺寸的足够时间。它将更新大小。如果它没有完成,它将被抢占,bytesSzNew将不会被复制到byteSzCode。@chibacity我知道如果它不能完成,它将通过-1。其效果将是抢占该线程。这是一个看门狗终止符,用于防止搜索目录结构中的循环引用。恐怕我需要某种方法来抢占线程,以便di.GetDirectories调用不会挂起循环sym链接引用。这就是我使用线程的全部目的。@John,你是说如果你有一个循环引用,它在进入foreach循环之前挂起了吗?是的,这在di的合同中。GetDirectories@John您可以考虑使用递归,而不是搜索。然后,您将能够退出循环,如果您愿意,还可以处理循环引用检测。递归是零,我需要更多地接触c和并行。我看到这两种解决方案现在都相当优雅。我将尝试使用类而不是结构。恐怕我需要一些方法来抢占线程,以便di.getDirectory调用不会挂起循环sym链接引用。这就是我使用线程的全部目的。@John,你是说如果你有一个循环引用,它在进入foreach循环之前挂起了吗?是的,这在di的合同中。GetDirectories@John您可以考虑使用递归,而不是搜索。然后,您将能够退出循环,如果您愿意,还可以处理循环引用检测。递归是零,我需要更多地接触c和并行。我看到这两种解决方案现在都相当优雅。我要尝试使用类而不是结构。这让我头脑清醒:这让我头脑清醒: