0

I'm developing a Windows Forms application with C#, .NET Framework 4.0 and Visual Studio 2012 Premium.

I have this method:

private void firstPhaseBtn_Click(object sender, EventArgs e)
{
    var task = Task.Factory.StartNew(() =>
    {
        if (_viewModel == null)
            _viewModel = new MainViewModel();

        this.BeginInvoke((Action)delegate()
        {
            labelLoading.Text = "Creando orden...";
            labelLoading.Visible = true;

            Models.FirstPhaseModel model = new Models.FirstPhaseModel()
            {
                // Set data.
            };

            orderNumberLabel.Text = _viewModel.FirstPhase(model);

            firstPhaseBtn.Enabled = true;
            labelLoading.Text = string.Empty;
            labelLoading.Visible = false;
        });
    });

    try
    {
        task.Wait();
    }
    catch (Exception)
    {
        MessageBox.Show(this, _viewModel.ClientCustomError, "Error");
    }
}

On _viewModel.FirstPhase(model); I do a HTTP Get Request to a web service.

My problem here is the try catch block doesn't work. I always get an unhandled exception.

I have tried to run the project in Debug, Release, and running the executable file on Release folder, but I always get an unhandled exception.

I have also tried to put the try catch block inside the task, but with the same result.

How can I handled an exception in a Task?

I have also tried add this:

.ContinueWith(t =>
            {
                labelLoading.Visible = false;
                labelLoading.Text = string.Empty;
                MessageBox.Show(this, _viewModel.ClientCustomError, "Error");
            },  TaskContinuationOptions.OnlyOnFaulted);

But I'm still getting a System.Reflection.TargetInvocationException.

VansFannel
  • 45,055
  • 107
  • 359
  • 626
  • 1
    I don't know of any reason why a try/catch around the code of the task would not work. Maybe you should show us that code, since that would be my recommendation for how to do it. – John Saunders Jun 19 '15 at 06:42
  • 1
    I suggest you look at some of the Related questions, especially http://stackoverflow.com/questions/7883052/a-tasks-exceptions-were-not-observed-either-by-waiting-on-the-task-or-accessi?rq=1. – John Saunders Jun 19 '15 at 06:44
  • @JohnSaunders I have tried what you have suggested me but it doesn't work. It is very strange. – VansFannel Jun 19 '15 at 07:01
  • **what** exception do you get? Also, you start a new Task but then almost immediately do `Invoke` to marshal your work back onto the UI thread. That's not going to gain you much. – Damien_The_Unbeliever Jun 19 '15 at 07:03
  • 2
    And even if you did let this code run on a thread pool thread, you're blocking the UI thread with a call to `Wait` anyway - again making it pointless to put this in a separate task. – Damien_The_Unbeliever Jun 19 '15 at 07:09

2 Answers2

1

Problem is here:

this.BeginInvoke((Action)delegate()
    {
        labelLoading.Text = "Creando orden...";
        labelLoading.Visible = true;

        Models.FirstPhaseModel model = new Models.FirstPhaseModel()
        {
            // Set data.
        };

        orderNumberLabel.Text = _viewModel.FirstPhase(model);

        firstPhaseBtn.Enabled = true;
        labelLoading.Text = string.Empty;
        labelLoading.Visible = false;
    });

Once you call BeginInvoke, you're effectively running things on the UI thread and out of the Task itself which in turn, means that TPL is no longer able to track and trace the exceptions and combining them into on AggregateException.

This is a much easier way to demonstrate the problem:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    protected override void OnShown(EventArgs e)
    {
        base.OnShown(e);
        var task = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(1000);
            BeginInvoke((Action)delegate { throw new NotImplementedException(); });
        });

        try
        {
            task.Wait();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

If you run this code, it will crash the application (unless you've subscribed to the relevant catch all exception handler which I can't remember anymore).

What you need to do is to either move the BeginInvoke code out (using task continuation on the correct scheduler) or try/catching the entirety of the code inside your BeginInvoke().

Update:

This is how I would rewrite your code:

var task = Task.Factory.StartNew(() =>
        {
            if (_viewModel == null)
            {
                _viewModel = new MainViewModel();
            }
        }).
            ContinueWith(x =>
            {

                labelLoading.Text = "Creando orden...";
                labelLoading.Visible = true;

                Models.FirstPhaseModel model = new Models.FirstPhaseModel()
                {
                    // Set data.
                };

                orderNumberLabel.Text = _viewModel.FirstPhase(model);

                firstPhaseBtn.Enabled = true;
                labelLoading.Text = string.Empty;
                labelLoading.Visible = false;
            }, TaskScheduler.Current).ContinueWith(result =>
            {
                if (result.IsFaulted)
                {
                    // do something with the result and "consume" it.
                    _log.Error(result.Exception);
                }
            });
kha
  • 19,123
  • 9
  • 34
  • 67
0

This is how I have fix my problem:

private void firstPhaseBtn_Click(object sender, EventArgs e)
{
    string orderNumber = string.Empty;

    labelLoading.Text = "Creando orden...";
    labelLoading.Visible = true;

    Models.FirstPhaseModel model = new Models.FirstPhaseModel()
    {
        // Data...
    };

    var task = Task.Factory.StartNew(() =>
    {
        if (_viewModel == null)
            _viewModel = new MainViewModel();

        orderNumber = _viewModel.FirstPhase(model);
    })
    .ContinueWith(result =>
    {
        if (result.IsFaulted)
        {
            this.BeginInvoke((Action)delegate()
            {
                labelLoading.Visible = false;
                labelLoading.Text = string.Empty;
                MessageBox.Show(this, _viewModel.ClientCustomError, "Error");
            });
        }
        else
        {
            this.BeginInvoke((Action)delegate()
            {
                orderNumberLabel.Text = orderNumber;

                firstPhaseBtn.Enabled = false;
                labelLoading.Visible = false;
                labelLoading.Text = string.Empty;
            });

            ShowStaticAttributes();
        }
    });
}

Thanks for your answers and help.

VansFannel
  • 45,055
  • 107
  • 359
  • 626