0

I am trying to run a series of tasks in the background of my Telnet / SSH program. sadly it seems I am not able to find out how to get this to work on the background.

I have tried using Tasks :

Task ReindexStock = new Task(delegate
        {
            this.Invoke(new Action(() =>
            {
                btnReindexStock.PerformClick();
                txtBoxInput.Text = command[1];
                ExecuteCommand();
            }
            ));
        });
        ReindexStock.Start();
        ReindexStock.Wait();

        Task Product_attribute = new Task(delegate
        {
            this.Invoke(new Action(() =>
            {
                btnReindexProduct_Attribute.PerformClick();
                txtBoxInput.Text = command[2];
                ExecuteCommand();
            }
          ));
        });
        Product_attribute.Start();
        Product_attribute.Wait();

I also tried threads :

new Thread(() =>
    {
        btnReindexStock.PerformClick();
        txtBoxInput.Text = command[1];
        ExecuteCommand();
    }).Start();
new Thread(() =>
    {
        btnReindexProduct_Attribute.PerformClick();
        txtBoxInput.Text = command[2];
        ExecuteCommand();
    }).Start();

as well as this ( plucked this one from the net, hoped it would work ):

          ThreadPool.QueueUserWorkItem(delegate
      {
          btnReindexStock.PerformClick();
          txtBoxInput.Text = command[1];
          ExecuteCommand();
      });
      ThreadPool.QueueUserWorkItem(delegate
      {
          btnReindexProduct_Attribute.PerformClick();
          txtBoxInput.Text = command[2];
          ExecuteCommand();
      });

But for some reason my program still freezes when it's doing ExecuteCommand (

  var cmd = SSH.client.CreateCommand(txtBoxInput.Text);
        var result = cmd.Execute();
        this.Invoke(new Action(() =>
        {
            rTxtBoxOutput.Text += result;

            var reader = new StreamReader(cmd.ExtendedOutputStream);
            rTxtBoxOutput.Text += "\n" + reader.ReadToEnd();
        }
        ));

)

I have tried several things in the backgroundworker_doWork aswell but none of them seemed to work.. I tried to start a new thread like this

new Thread(new ThreadStart(ReindexAll)).Start();

also like this but I guess this is practically the same but larger

Thread t = new Thread(new ThreadStart(ReindexAll));
            t.Start();
            t.IsBackground = true;

and

Task.Factory.StartNew(() => ReindexAll());

as well as the plain and simple

ReindexAll();

but as I said before none of it seems to work, the moment I execute my command the program freezes.

Now my question is if someone is able to tell me what I am doing wrong and hopefully help me

2 Answers2

0

From most of your examples, it looks like you're trying to click a button on your background thread to start performing whatever task this is.

all UI is only able to work on the UI thread. Most of this would almost definitely cause an error, I would think. If you're trying to do something strictly off of the UI thread, you can. However, you shouldn't be triggering a button on another thread to do so. Typically speaking, the button should be triggered by your operator only.

If you need to do some work without the operator's involvement, you can either do it in the start of the app when everything is loading up, or you can set a timer and trigger the work via that. Again, this shouldn't trigger a button, but the work itself.

oppassum
  • 1,746
  • 13
  • 22
  • well, my vs doesn't give me errors while executing my code. it runs through it but does everything on the UI thread which I am trying to avoid. The reason I need some buttons clicked on another thread is because I am getting an error if I do not. for what I am trying to do I need the buttons clicked after the completion of each task. since it is a telnet program I can't simply run it from startup or set a timer because I don't know what I'll be pressing. I have a list of options on the left side of my SSH program to execute specific commands once I press on execute – Jeffrey Ullers Jan 29 '18 at 21:09
  • You cannot modify UI items on a non-UI thread. I'm not sure what the rest of your setup is, but you may want to start there. – oppassum Jan 29 '18 at 21:12
  • Just wanted to add something: "cannot modify UI items on a non-UI thread" is misleading. You can modify a UI item from a non-UI thread through the use of callback (which technically is kicking over to the UI thread.) – Kevin Jan 29 '18 at 22:06
  • @Kevin do you happen to know what I could do to fix this? I'm kinda stuck on this :S – Jeffrey Ullers Jan 30 '18 at 12:30
  • @JeffreyUllers what is the next execution of code after the button clicks? I think you just need to not do button clicks and execute the code direct. Kevin you said it exactly right... you kick back over to the UI thread. I think I mentioned various ways of doing that (dispatcher, etc), but that's still executing on the UI thread, which will still "freeze" or take priority away from the UI. – oppassum Jan 30 '18 at 13:50
  • Ugh, all I was doing was just commenting that the phrase "cannot modify UI items on a non-UI thread" is both technically correct and a bit misleading. Okay, I'll look through the question some more to see if I can post my own answer for the problem. – Kevin Jan 30 '18 at 14:10
0

Okay, first up - I'd suggest simplifying the stuff where you're creating Tasks. Instead of trying to write an inline delegate, just break out the Task's code into a separate function, and then have the Task refer to it. Much simpler, much cleaner.

Task myTask = new Task(TaskCode);

// elsewhere ...

private void TaskCode()
{
    // stuff
}

At that point, after creating myTask, you can simply call myTask.Start() and you're done.

Now, that's only one half of your problem. If you tried this:

private void TaskCode()
{
    SomeGuiControl.Text = "something";
}

... you're going to get an error that you can't update a GUI control from the non-gui thread. However, you can use Invoke or BeginInvoke to kick over a message to the GUI thread to handle an update (see: Writing to a TextBox from another thread?) The simplest being something like:

txtResults.BeginInvoke(new Action(() => txtResults.Text = DateTime.Now.ToString() + " Hello World"));

Finally, there's one additional caveat that's very important. You shouldn't do a Task.Wait() on the GUI thread. Why? Because all the GUI events won't fire off until the Task.Wait() finishes - which means, if you're doing updating of the GUI card throughout the task, it won't show up until everything is done! Keep in mind, the idea is to keep the GUI thread's code as quickly-done as possible. Kick off the thread and exit out - keep the GUI thread free to process other events (user or otherwise.)

Putting it all together? Here's what my sample code looks like on a WinForm I created for this problem:

private void btnSmallJob_Click(object sender, EventArgs e)
{
    Task myTask = new Task(SmallTaskCode);
    myTask.Start();  // NOTE: I'm NOT doing a wait() on this task; don't want to hold up the GUI thread.

}

private void SmallTaskCode()
{
    System.Threading.Thread.Sleep(1000);
    txtResults.BeginInvoke(new Action(() => txtResults.Text += DateTime.Now.ToString() + " Small Job" + Environment.NewLine));
    System.Threading.Thread.Sleep(1000);
}

Task singleInstanceOfLargeJob;

private void btnLargeJob_Click(object sender, EventArgs e)
{
    if (this.singleInstanceOfLargeJob == null || this.singleInstanceOfLargeJob.Status != TaskStatus.Running)
    {
        singleInstanceOfLargeJob = new Task(LargeTaskCode);
        singleInstanceOfLargeJob.Start();  // NOTE: I'm NOT doing a wait() on this task; don't want to hold up the GUI thread.
        return;
    }
    MessageBox.Show("Sorry, you can only have one instance of the large job running at once!");
    // this job should only have one instance running at a time!
}

private void LargeTaskCode()
{
    System.Threading.Thread.Sleep(1000);
    txtResults.BeginInvoke(new Action(() => txtResults.Text += DateTime.Now.ToString() + " Big Job A" + Environment.NewLine));
    System.Threading.Thread.Sleep(1000);
    txtResults.BeginInvoke(new Action(() => txtResults.Text += DateTime.Now.ToString() + " Big Job B" + Environment.NewLine));
    System.Threading.Thread.Sleep(1000);
    txtResults.BeginInvoke(new Action(() => txtResults.Text += DateTime.Now.ToString() + " Big Job C" + Environment.NewLine));
    System.Threading.Thread.Sleep(1000);
}

Basically, I've got it two different ways. The first half is just a simple, kick-off-the-thread-and-done. The second keeps track of the Task, and doesn't allow two of the job to be running at the same time.

Anyway, hope that helps!

Kevin
  • 2,133
  • 1
  • 9
  • 21