hasan basri
hasan basri

Reputation: 13

How to use property of event args with async methods?

How to use properties in async methods if we want to write it to a control.It throws an exception when trying to write without invoke.So I want to handle invoking problem in Counter class not in Form1. I have written an example for this problem. In this code TextBox.Text line throws cross thread exception.

internal class Form1 : Form        
{
    public Form1()
    {
         InitializeComponent();
    }
     private void button1_Click(object sender, EventArgs e)
    {
        Counter myCounter = new Counter(1000);
        myCounter.IndexValueChanged += myCounter_IndexValueChanged;
        myCounter.StartCountAsync();

    }

    void myCounter_IndexValueChanged(object sender, IndexValueChangedEventArgs e)
    {
        textBox1.Text = e.Index.ToString();
    }

}
class Counter
{
    public delegate void IndexValueChangedEventHandler(object sender, IndexValueChangedEventArgs e);
    public event IndexValueChangedEventHandler IndexValueChanged;

    int _maxNumber;
    int _index;
    public Counter(int maxNumber)
    {
        _maxNumber = maxNumber;
    }
    public async void StartCountAsync()
    {
        await Task.Run(() =>
        {
            for (int i = 0; i < _maxNumber; i++)
            {
                _index = i;
                if (IndexValueChanged != null)
                    IndexValueChanged(this, new IndexValueChangedEventArgs(_index));
                Thread.Sleep(100);

            }
        });
    }
}
class IndexValueChangedEventArgs
{
    int indexNum;
    public IndexValueChangedEventArgs(int index)
    {
        indexNum = index;
    }
    public int Index
    {
        get { return indexNum; }
    }
}`

Upvotes: 1

Views: 174

Answers (3)

user743382
user743382

Reputation:

In your particular example, it doesn't make sense to me to use a background thread in the first place. Sure, if you call Thread.Sleep on the UI thread, that's bad for obvious reasons, but you don't need to call Thread.Sleep to get an async method to wait.

public async void StartCountAsync()
{
    for (int i = 0; i < _maxNumber; i++)
    {
        _index = i;
        if (IndexValueChanged != null)
            IndexValueChanged(this, new IndexValueChangedEventArgs(_index));
        await Task.Delay(100);
    }
}

If called from the UI thread, then after the delay, this method will continue execution on the UI thread, so the invocation of IndexValueChanged will just work.

Upvotes: 1

Oguz Ozgul
Oguz Ozgul

Reputation: 7187

You can change the behaviour of your application to allow cross thread calls to modify your form elements:

System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false;

Or, if people say that this is not recommended and not safe and is not proffessional,

you can use the AsyncOperationManager in your Counter class. The WebClient.DownloadFileAsync method uses the same internally. This will ensure that the event handler will be called on the UI thread.

Here is your final Counter class which should be calling the delegate on the UI thread..

class Counter
{
    public delegate void IndexValueChangedEventHandler(object sender, IndexValueChangedEventArgs e);
    public event IndexValueChangedEventHandler IndexValueChanged;

    int _maxNumber;
    int _index;

    public Counter(int maxNumber)
    {
        _maxNumber = maxNumber;
    }

    public async void StartCountAsync()
    {
        AsyncOperation asyncCountOperation = AsyncOperationManager.CreateOperation(null);

        await Task.Run(() =>
        {
            for (int i = 0; i < _maxNumber; i++)
            {
                _index = i;
                asyncCountOperation.Post(new SendOrPostCallback(InvokeDelegate), _index);
                Thread.Sleep(100);
            }
        });
    }

    private void InvokeDelegate(object index)
    {
        if (IndexValueChanged != null)
        {
            IndexValueChanged(this, new IndexValueChangedEventArgs((int)index));
        }
    }
}

Upvotes: 0

Ren&#233; Vogt
Ren&#233; Vogt

Reputation: 43936

To invoke a delegate on a ui thread you need a windows handle. I don't know why you want to do the invoke inside the Counter class, but the easiest way seems to me: give your Counter instance a reference to your Form and call the Invoke-Method on that reference. (Writing from my phone, so it's hard to add code sample, will edit tomorrow)

EDIT: but I really think it's the responsibility of the Form to check that it makes changes in the correct thread and therfore calls (Begin)Invoke itself.

EDIT: Here is your Counter class with the added "parent" reference:

class Counter
{
  public delegate void IndexValueChangedEventHandler(object sender, IndexValueChangedEventArgs e);
  public event IndexValueChangedEventHandler IndexValueChanged;

  int _maxNumber;
  int _index;
  Control _parent;

  public Counter(Control parent, int maxNumber)
  {
    _maxNumber = maxNumber;
    _parent = parent;
  }
  public async void StartCountAsync()
  {
    await Task.Run(() =>
    {
        for (int i = 0; i < _maxNumber; i++)
        {
            _index = i;
            // introduce local variable for thread safety
            IndexValueChangedEventHandler handler = IndexValueChanged;
            if (handler != null)
            {
                if (_parent == null || !_parent.InvokeRequired)
                    handler(this, new IndexValueChangedEventArgs(_index));
                else
                    // use BeginInvoke
                    _parent.BeginInvoke(handler, this, new IndexValueChangedEventArgs(_index));
            }
            Thread.Sleep(100);
        }
    });
  }
}

And you use it in your button handler as this:

private void button1_Click(object sender, EventArgs e)
{
    Counter myCounter = new Counter(this, 1000);
    myCounter.IndexValueChanged += myCounter_IndexValueChanged;
    myCounter.StartCountAsync();

}

And it is better to use BeginInvoke in the Counter class, because Invoke would wait until the UI thread executed the delegate and so your Counter may count slower than expected (or your web client will use the network resources longer than needed). Hope this helped you.

EDIT: introduced local variable "handler". Otherwise it may be possible that the consumer deregistered the event after you null-checked it and it is null when you call it.

Upvotes: 1

Related Questions