Search code examples
c#multithreadingwinformscancellationcancellationtokensource

TargetInvokationException in Application.Run(new Form1());


I am trying to iterate through a for loop by pressing start button and stop it by pressing stop button. I am using await Task.Run(() => it works in the expected manner. But when I press start button again, I get TargetInvokationException in Application.Run(new Form1());.

My code below

using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CancellationTest
{
    public partial class Form1 : Form
    {
        private readonly SynchronizationContext synchronizationContext;
        private DateTime previousTime = DateTime.Now;

        CancellationTokenSource cts = new CancellationTokenSource();

        public Form1()
        {
            InitializeComponent();
            synchronizationContext = SynchronizationContext.Current;
        }

        private async void ButtonClickHandlerAsync(object sender, EventArgs e)
        {
            button1.Enabled = false;
            var count = 0;

            CancellationToken token = cts.Token;

            await Task.Run(() =>
            {
                try
                {
                    for (var i = 0; i <= 5000000; i++)
                    {
                        token.ThrowIfCancellationRequested();

                        UpdateUI(i);
                        count = i;
                    }
                }
                catch (System.OperationCanceledException)
                {
                    MessageBox.Show("Canceled");
                }
            }, token);

            label1.Text = @"Counter " + count;
            button1.Enabled = true;
        }

        public void UpdateUI(int value)
        {
            var timeNow = DateTime.Now;

            if ((DateTime.Now - previousTime).Milliseconds <= 50) return;

            synchronizationContext.Post(new SendOrPostCallback(o =>
            {
                label1.Text = @"Counter " + (int)o;
            }), value);

            previousTime = timeNow;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            cts.Cancel();
        }
    }
}

Can anyone explain why this happens and how to resolve this.


Solution

  • Can anyone explain why this happens

    TargetInvokationException is a wrapper type exception and the main information is contained in InnerException property which you didn't show. Looking at the code, most likely it is OperationCanceledException and is caused by passing an already canceled token to Task.Run.

    In general you should not reuse the CancellationTokenSource instance - see The general pattern for implementing the cooperative cancellation model in the Remarks section of the documentation.

    Also you should protect with try/catch the whole block, not only the Task.Run body. And initialize all the involved state members at the beginning, and do the necessary cleanup at the end.

    So the correct code could be like this:

    private DateTime previousTime;
    private CancellationTokenSource cts;
    
    private async void ButtonClickHandlerAsync(object sender, EventArgs e)
    {
        button1.Enabled = false;
        var count = 0;
        previousTime = DateTime.Now;
        cts = new CancellationTokenSource();
        try
        {
            CancellationToken token = cts.Token;
            await Task.Run(() =>
            {
                for (var i = 0; i <= 5000000; i++)
                {
                    token.ThrowIfCancellationRequested();
                    UpdateUI(i);
                    count = i;
                }
            }, token);
        }
        catch (System.OperationCanceledException)
        {
            MessageBox.Show("Canceled");
        }
        finally
        {
            cts.Dispose();
            cts = null;
        }
        label1.Text = @"Counter " + count;
        button1.Enabled = true;
    }
    

    and make sure Cancel button is enabled only when cts != null or check that condition inside the click handler in order to avoid NRE.