开发者

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜