Search code examples
c#multithreadingactionanonymous-functionblockingcollection

BlockingCollection worker needs to return a value via anonymous function


I have a tricky situation and was wondering if anyone could shed any light on the matter:

I have a blocking collection action worker that is called like this

 ultraHash hash = new ultraHash(lblFolder.Text, (Action) (() => {
       textBox1.Text = self.getMD5();
 });
 runQueue.addAction(hash);

now the "self" thing is there is pseudo code, and this is why, this is the worker (where runQueue is the BlockingCollection container class)

class ultraHash
{
    string _filePath;
    Action _onComplete;
    string _md5;
    public ultraHash(string filePath)
    {
        _filePath = filePath;
    }

    public ultraHash(string filePath, Action onComplete) : this(filePath) //constructor chaining
    {
        _onComplete = onComplete;
    }

    public override void action()
    {
        using (var md5 = MD5.Create())
        {
            try
            {
                using (var stream = File.OpenRead(_filePath))
                {
                    _md5 = MakeHashString(md5.ComputeHash(stream));
                    if (_onComplete != null) _onComplete();
                }
            }
            catch (IOException)
            {
                /***
                 * file is Busy
                 */
            }
        }
    }

    public string getMD5()
    {
        return _md5;
    }

    private string MakeHashString(byte[] hashBytes)
    {
        StringBuilder hash = new StringBuilder(32);
        foreach (byte b in hashBytes)
        {
            hash.Append(b.ToString("X2").ToLower());
        }
        return hashBytes.ToString();
    }
}

now what I want to happen is for the anonymous method Action (() => {} in the first snippet to execute after the queue has completed and be able to contain the MD5 sum. I know there is thread safety issues here, so I understand I have to invoke this back to the parent thread where textBox1 resides, but at the moment I am just lost as to how to do this (is it even possible?)

EDIT

I don't normally "edit" answers but in case anyone else hits this issue this is the code I eventually used, thanks to the answer from usr. Notice the BeginInvoke

  ultraHash hash = null;
  hash = new ultraHash(lblFolder.Text, (Action) (() => {
       this.BeginInvoke((Action) (() => {
            txtMain.Text = hash.getMD5();
        }));
  }));
  runQueue.addAction(hash);

also, if you are wondering why I did this, it is merely so that I can "watch" a folder for any file changes and then store meta data changes, since entire folders can be dragged in/out there needs to be a queueing mechanism in a FIFO queue, also the parent folders are hashed too, so any file alterations need to bubble up, lastly it may be that a file is locked when it is trying to be hashed in which case a FIFO queue can wait


Solution

  • Probably, you should pass the hash as an argument to your onComplete function. getMD5 does not need to exist.

    If you insists on doing it just this way you need a little initialization dance:

    ultraHash hash = null;
    hash = new ultraHash(lblFolder.Text, (Action) (() => {
               textBox1.Text = hash.getMD5();
         });
    

    The whole design of the ultraHash seems strange. Is it really necessary for that class to exist? A single static method that computes the hash should be enough.