Click here to Skip to main content
15,074,394 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
See more:
Hello Everyone! i read this article here and it had an example of how to do operations on a separate thread using a class, it also had an event where when the operation on the new thread completed it would report that the operation was complete to the winform(parent thread) and the winform would print that the operation was completed to the UI. But the problem is that this was in VB.NET, so i attempted to convert this class in to C# but i had no success. so my question is how do i create a class that spawns off a thread that runs a task that i specify, i also want to know how i would make that newly spawned thread report: A. Its current progress in the current operation, B. when it completes(when the progress integer reaches 100) it fires an event that tells the winform to run a method that tells the user that the operation was completed. here is what i have for the thread class so far:

C#
using System;
using System.Speech.Synthesis;
using System.Threading;
public class ManualThread
{
    //Declares The Event That Fires When The Operation Has Completed
    public delegate void CountCompleted();

    EventArgs e = new EventArgs();
    //Declares A New Instance Of The Delegate
    public event CountCompleted CountCompletedEvent;

    //Declares the variables you will use to hold your thread objects.
    public Thread Thread1;

    #region Properties
    //This Property Will Be Set In The Winform Class
    public string TextToSpeak { get; set; }

    #endregion

    public ManualThread()
    {
        this.CountCompletedEvent += new CountCompleted(this.CountCompletedEventFiredMethod);
    }

    public void StartCounting()
    {

        //Sets the copy and count threads using the AddressOf the subroutine where
        //the thread will start.
        //The Above Comment Is For The VB Version Ignore This Comment
        Thread1 = new System.Threading.Thread(this.Count);
        Thread1.IsBackground = true;
        Thread1.Name = "Thread1";
        Thread1.Start();

    }

    private void Count()
    {
        //Declare a new instance of the SpeechSynthesizer class
        SpeechSynthesizer synth = new SpeechSynthesizer();

        //Set The Audio Output Device To The Defualt Output Device
        synth.SetOutputToDefaultAudioDevice();

        // Select a voice that matches a specific gender.
        synth.SelectVoiceByHints(VoiceGender.Female);

        // Speak The Contents Of The "TextToSpeak" Property Synchronously(Only because this will be done on a new thread).            
        synth.Speak(TextToSpeak);

        //Raise the copy status event at the end of the program loop
        CountCompletedEvent();

    }
    private void CountCompletedEventFiredMethod()
    {
        //tell the winform class to run a specific method here
        //i think this would require either a message or a Invoke call
    }
}


then at the winform class level i declare a new instance like this:
C#
ManualThread ThreadOperation = new ManualThread();

then when ever i want to callit i just do this:
C#
ThreadOperation.TextToSpeak = TB.Text;
ThreadOperation.StartCounting();


now the operation runs fine but it can't actually report that the operation has completed to the winform(this is due to the fact that the
CountCompletedEvent(this, e);
gives me a null reference exception error, and therefor doesn't fire the event. therefor i would like to ask 2 things:
1. how would i fix this error. 2. if i can't fix this error how would i make a class that can spawn a new thread that runs an operation, and then fires an event when the operation is completed.
and yes i have read many articles here on codeproject talking about how to do mutlithreading on console and winform apps. but none of them i understood, but the article i read(linked above) i understood but as i said it was done in VB.NET
--UPDATE--
oh and also i am not very experienced with delegates or events(i think delegates have something to do with events) so please excuse my lack of understanding in this area.

NOTE: The class "ManualThread" is not mine but i converted from VB into C#, and it was
taken from the article linked above.

--UPDATE2--
i have figured out how to add an event handler to the "ManualThread" class, i did it like this(i had to add a constructor):
C#
public ManualThread()
{
    this.CountCompletedEvent += new CountCompleted(this.Test);
}

but now all it does is fire the event and... and... nothing.
i am successfully firing the event i just don't know how to get the winform class to here the firing of the event so any help on that please?
thank you for all of your help in advance,
MasterCodeon
Posted
Updated 24-Dec-14 4:09am
v8
Comments
BillWoodruff 23-Dec-14 12:14pm
   
I'd like to suggest you examine using a BackGroundWorker which is a simpler method that supports notifications; if it meets your needs it will be easier to use than creating your own Threads directly, or moving to the cutting-edge facility in the newer Task Library.

Good example here:

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx

I believe that mastering using a BackGroundWorker will serve you well as you go on to manipulate threads directly.
MasterCodeon 23-Dec-14 12:19pm
   
i have considered using a BackgroundWorker but i wanted to use threads because of A: i would learn how to manipulate threads and how to be able to work with them in. B. how a class is properly constructed(this is an area i have been having a really hard time with) C. using threads is more flexible than doing this the easy way and using a BackgroundWorker. so in short i am trying to do this the hard way because its more flexible and i will learn a lot from this method.
and what is a "Task Library"? i have never heard of that.
MasterCodeon 23-Dec-14 12:23pm
   
i am very good at using BackgroundWorkers but i am not so good at using threads.
i know the basic concepts of both of them but i am not really good at implmenting those concepts when it comes to threads
MasterCodeon 23-Dec-14 12:28pm
   
and yes a BackgroundWorker does meet my needs.
you see i like to learn how to do challenging things first and then learn the simpler things Like for example: if i can master using threads directly then using BackgroundWorkers will be easy as pie. this is how i learn when it comes to programming.
(I hope you understood that because i have explained this to other people and they didn't get it because this is a very different way of thinking.)
BillWoodruff 23-Dec-14 12:57pm
   
I think it's great you want to challenge yourself ! cheers, Bill
MasterCodeon 23-Dec-14 13:34pm
   
can you take a look at my post i have updated it

In C#, you have to verify that the event is not null before calling it. It will be null if no handler were attached to it. Thus, you should write:
C#
if (CountCompletedEvent != null) { CountCompletedEvent(this, e); }

Also, you should use BackgroundWorker as is it much more easier to use as it will properly handle reporting progress in the UI thread using ReportProgress function amoung other things.
If as you said, you are not very experimentes, then it is hard to understand why you want to do thread handling manually while the framework already has a component for that. You can even drop it from then toolbox onto a form and a few clicks you can set and activate handlers. Very, very, very easy to use.
   
v2
Comments
MasterCodeon 23-Dec-14 12:48pm
   
yeah i don't have an event handler for the event and i don't know how to setup one.
and i have already tried this and it didn't work for the fact as like you said there is no event handler so it will be null when it reaches the if statement.
so this doesn't really help, but you telling me that "It will be null if no handler were attached to it." that explains why the event is null.
thanks you for your help.
MasterCodeon 23-Dec-14 12:49pm
   
oh and as regards to the background worker thing, read my comments in the original post.
i have already explained this to Bill
Philippe Mori 23-Dec-14 13:40pm
   
There are many subtilities to know if you want to do this manually. You have to learn threading first otherwise your program will broke and you won't know why. See http://msdn.microsoft.com/library/ms173178.aspx for more information.
In you case, you will have to learn how to use Invoke and also manage the lifetime of your thread and event.
By the way, you generally have to use Invoke to call an event in the UI thread.
MasterCodeon 23-Dec-14 13:59pm
   
i know the basics of how to use create and use a thread.
but how do i use the Invoke method to call the "CountCompletedEvent" on my UI thread?
Sergey Alexandrovich Kryukov 23-Dec-14 14:12pm
   
I guess it solves main OP's problem (my 5), but OP apparently has a lot more problem, hard to understand where to start.
I just added the answer on automatic translations between VB.NET and C#, and (after some thinking) another one on UI thread invocation.
—SA
First of all, you can translate any assembly from VB.NET and C# automatically, with guaranteed results. It's the best to do it with the whole assembly, using ILSpy. Please see my past answer: Code Interpretation, C# to VB.NET[^].

—SA
   
Your other problem is: what you are going to do with the notification from some non-UI thread, be it notification on completion or anything else. You cannot call anything related to UI from non-UI thread. Instead, you need to use the method Invoke or BeginInvoke of System.Windows.Threading.Dispatcher (for both Forms or WPF) or System.Windows.Forms.Control (Forms only).

You will find detailed explanation of how it works and code samples in my past answers:
Control.Invoke() vs. Control.BeginInvoke()[^],
Problem with Treeview Scanner And MD5[^].

See also more references on threading:
How to get a keydown event to operate on a different thread in vb.net[^],
Control events not firing after enable disable + multithreading[^].

—SA
   
Comments
MasterCodeon 23-Dec-14 14:37pm
   
ok I have read your post on the Invoke vs. BegineInvoke.
i still don't get it
i am trying to use a BeginInvoke statment like this:

private void CopyFiles_CountCompleted(object sender, EventArgs e)
{
//Handles CopyFiles.CountCompleted
//BeginInvoke causes asynchronous execution to begin at the address
//specified by the delegate. Simply put, it transfers execution of
//this method back to the main thread. Any parameters required by
//the method contained at the delegate are wrapped in an object and
//passed.
this.BeginInvoke(new ManualThread.CountCompleted(ThreadOperationCompleted));
}

private void ThreadOperationCompleted()
{
txtSpoken.Text += "It Worked!";
}
but i don't understand how the "CopyFiles_CountCompleted" method is supposed to be ran when the "CountCompletedEvent" is fired.
i think i have to make the "CountCompletedEvent" event fire this method right?
if so how would i possibly do that?
i am sorry that i am not getting it (i can be kind of thick headed sometimes)
Sergey Alexandrovich Kryukov 23-Dec-14 16:46pm
   
And..?

Sorry, what are you not getting? What does it mean "how... it is supposed to be run?"? And "to make the event"? Do what? Perhaps you need to learn how event work and how thread works. The invocation calls the method in the UI thread. What is unclear here?

—SA
MasterCodeon 24-Dec-14 9:52am
   
what i don't know is how the "CountCompletedEvent"(which is in the "ManualThread" class event is supposed to fire the "CopyFiles_CountCompleted"(which is in the winform class) method which does runs the BeginInvoke method.
what is not clear is how i am supposed to invoke a method not in the "ManualThread" class but in the winform class.
MasterCodeon 24-Dec-14 9:58am
   
you see its like this:
an event fires in the "ManualThread" class, that triggers a method in the "ManualThread" class. Then that method that was triggered tells the class that holds the winform app to run a specific method(that is in the winform class)
this is what i want to do.
the poster of the article i read did this in VB so i know its possible, i just don't know how to do it.
MasterCodeon 24-Dec-14 10:10am
   
i updated my post with the latest version of the "ManualThread" class.
it also has clearer comments.
It's actually easier to write a simple example that demonstrates creating a new
Thread with a "call-back" function that executes from the Thread that returns some data to the WinForm application that creates the Thread, and starts it ... than it is for me to understand your code as it is now. That may reflect my "holiday state-of-mind" more than it says anything about your code ! :)
C#
// required
using System.Threading;

// the delegate
public delegate void ResultOfPerformCount(int result);

// will hold instance of the delegate
public ResultOfPerformCount showResult;

// some Button
private void btnStartThread_Click(object sender, EventArgs e)
{
    Thread newTestThread = new Thread(PerformCount);
    newTestThread.IsBackground = true;
    newTestThread.Name = "newTestThread";

    // assign executable code to instance of delegate
    showResult = ShowResult;

    // start the thread
    newTestThread.Start(100);
}

// what the Thread will execute
private void PerformCount(object data)
{
    int limit = Convert.ToInt32(data);
    int count = 0;

    for (int i = 0; i < limit; i++)
    {
        count++;
        Thread.Sleep(10);
    }

    // test for instance of delegate is null
    if (showResult != null) showResult(count);
}

// show the result
private void ShowResult(int result)
{
   MessageBox.Show(string.Format("count is now: {0}", result));
}
I didn't go to the trouble of wrapping the business with the Thread in a Class, but I think that's not something you'll have a problem with.
   
Comments
MasterCodeon 24-Dec-14 9:53am
   
yeah i could easily wrap that in a class
Philippe Mori 24-Dec-14 11:02am
   
I don't see what this solution gives that is not already in OP question except that you add the check I mentionned in Solution 1.

And your code does not handle cross-threading calls. It might works with a message box but you'll get an exception if you try to update most UI controls and for what your handler does, a partial method would be enough.
MasterCodeon 24-Dec-14 12:01pm
   
yeah thats the problem that i had with this solution
BillWoodruff 24-Dec-14 13:15pm
   
Anytime you have a problem with a solution, it's a thoughtful thing to let the author of the solution know what the problem is, and it's a very good thing to do that as soon as possible. Sometimes it takes a back-and-forth to "get it right."

Merry Xmas, Bill
BillWoodruff 24-Dec-14 13:14pm
   
At the time I wrote my post, not all the information was present that there is now. I wrote because, at that time, I felt the OP could benefit from a clear example that was tested, and working.

And, if I judged your post by the same standards you apparently judged mine, I would probably down-vote it because: 1. you echo the advice I had already posted in a comment to use BackGroudWorker; 2. you did not address thread-safety, or UI update.

But, it's Christmas, and I'd rather be a Happy Santa than a ... Judge.

Merry Xmas, Bill
MasterCodeon 24-Dec-14 13:23pm
   
oh i wasn't trying to judge your post.
it was great and was well done.
its just that i didn't really solve the problem of cross-threading calls.
thank you this does help me to understand better how my class works though.
thank you
BillWoodruff 24-Dec-14 21:19pm
   
Hi MasterCodeon, My remarks were not a response to you; they were a response to Philip Mori. cheers, Bill

p.s. never feel like you "owe" anybody either an up-vote, or an "accept solution:" quality and relevance should always be the "winners" here; and, I'd say most of us here trying to be helpful are really in it for more than just "reputation points."
MasterCodeon 25-Dec-14 10:45am
   
oh ok, and your solution was relevant at the time you posted it, thats why i accepted it. it was also very helpful to(in the sense that you showed me a much simpler way to do a counting operation in a separate thread).
Philippe Mori 25-Dec-14 19:22pm
   
I really don't understand... I have checked the question history and I still don't see what this solution give that was not already known or is not trivial.
Hello Everyone! i have solved the last part of this problem!
i got the idea to use a windows message to notify the UI thread that the operation has completed. first i had to import the SendMessage method from the windows API like so:
C#
//Put this above your class
using System.Runtime.InteropServices;

//Put this in your class but put it above the constructor
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int SendMessage(int hWnd, int msg, int wParam, IntPtr lParam);

then i setup a property that holds the window handle that the message will be sent to:
C#
//This Property Will Be Set In The Winform Class As Well
public IntPtr ParentHandle { get; set; }

now when i call the class i set the "ParentHandle" property like this:
C#
ThreadOperation.ParentHandle = this.Handle;

then when the operation i am doing on the other thread completes, it will fire an event which, sends a message to the UI thread, this lies in the Event_Fired method:
C#
//Send A Message To The Parent Thread To Notify It That The Operation Has Completed
SendMessage(ParentHandle.ToInt32(), 0, 0, IntPtr.Zero);


now in order to recive the message on the UI thread i just overide the WndProc like this:
C#
protected override void WndProc(ref Message m)
{
    if (m.Msg == 0)
    {
        EventArgs e = new EventArgs();
        if (ManualThreadOperationCompleted != null)
        {
            //This Fires The ManualThreadOperationCompleted Event
            ManualThreadOperationCompleted(this, e);
        }
}


and there we have it!
A great way to do cross-threading method calls.
MasterCodeon
   
v2
Comments
Philippe Mori 25-Dec-14 18:39pm
   
Although it works, it is not elegant. You really don't want to do that in production code when you can use BackgroundWorker... By the way, if you would have followed what were mentioned in solutions 1 to 3, you would have come to an alternate solution that would not depends on Windows API.

In fact, your solution is bad as if you have to start 2 threads, you event handler won't even know which thread was completed for one thing and the other very bad thing is that you use 0 as a windows message. If you want to send custom message then you should use messages in the range reserved for that or register a message. In any case, you should uses parameters to send appropriate information.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900