将(结构体的)实例方法传递给 ThreadStart 似乎会更新虚假实例,因为原始实例不受影响

发布于 2024-12-03 06:58:27 字数 1804 浏览 2 评论 0原文

我的问题是将 this.folderFolder 实例方法传递给 ThreadStart ctor。我使用 dirAssThread 逐步执行它,并观察它正确并完成更新实例数据成员,然后我返回

if (dirAssThread.IsAlive) completeThread(-1); //***ie abort

并发现与我相同的 this 实例的数据成员通过该方法传递给 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);
        }
    }
}

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
    {
        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);
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

述情 2024-12-10 06:58:27

更新

问题标题更新后,您看到的问题是结构是在引用时复制的。在将委托分配给线程时,您正在传递结构的副本,并且该副本将由线程更新。当您在 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;
        }
    }
}

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;
        }
    }
}
吃→可爱长大的 2024-12-10 06:58:27

您在这里遇到的问题是,您将 struct 的方法传递给 ThreadStart 构造函数,并导致它创建 struct 的副本code> 实例并调用副本上的方法。您的代码正在运行,但它正在更新副本而不是原始实例。

尝试将 struct 更改为 class,您应该会看到问题消失。

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.

心清如水 2024-12-10 06:58:27

这是值(结构)类型的细微差别之一。使用值类型的理由很少——而且在很多情况下它们都会增加开销(而不是像您想象的那样消除它)。

只需将类型声明更改为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);
        }
    }

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);
        }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文