Fredrik Enqvist
Fredrik Enqvist

Reputation: 47

Access object in a different thread

Setup:

Win10 .NET 4.7.1/VS2017 .NET 4.5/ C#

Level:

Beginner/Intermediate/new to threading

Objective:

1: A selenium web automation class that is triggered by a timer class so that the web automation class can exchange data with a javascript site at specific times.

2: Should be possible to migrate solution from WebForms to .NET library (dll).

Problem:

Step 1. Timer class sends time event to method in Web class to login to internet site = working.

Step 2. Web automation class (WinForms/GUI) tries to retrieve data from the method that is triggered by timer class event = Exception: "Calling thread cannot access this object because a different thread owns it." (as translated from swe).

I admit I´m confused by the terminology in the area of threading that is new to me. Also, I understand some multithreading techniques are only valid for WinForms. Since my objective is to migrate the solution to a dll these are not an option for me. I´ve played around with Invoke(), but as I understand it´s limited to use in WinForms. Guidance is highly appreciated!

WEB AUTOMATION CLASS:

    private EdgeDriver driver;
    private SeleniumHelper helper;
    private WebAutomationTimer timer;
    private double account;

    public double Account { get => this.account; set => this.account = value; }

    public Form1()
    {
        InitializeComponent();
        timer = new WebAutomationTimer(02, 36, 00, 02, 38, 00);
        timer.OnLoginTime += Timer_OnLoginTime;
        timer.OnLogoutTime += Timer_OnLogoutTime;
    }

    private void Timer_OnLoginTime()
    {
        Login();
    }

    private void Timer_OnLogoutTime()
    {
        Logout();
    }

    public bool Login()
    {
        try
        {
            // working login code

    UpdateLabels();
        }
        catch (Exception e)
        {

        }
    }

    private void UpdateLabels()
    {
        // EXCEPTION !!!
        lblAccount.Text = GetAccount(); 
    // EXCEPTION !!!
    }

TIMER CLASS:

class WebAutomationTimer
{
    public event TimerEvent OnLoginTime;
    public event TimerEvent OnLogoutTime;
    //public event TimerEvent OnSecond;
    private System.Timers.Timer timer;
    private DateTime now;
    private int loginHour;
    private int loginMin;
    private int loginSec;
    private int logoutHour;
    private int logoutMin;
    private int logoutSec;

    public WebAutomationTimer(int loginHour, int loginMin, int loginSec, int logoutHour, int logoutMin, int logoutSec)
    {
        timer = new System.Timers.Timer();
        timer.Interval = 1000; // 1 sec
        timer.Elapsed += Timer_Elapsed;
        timer.Start();
        this.loginHour = loginHour;
        this.loginMin = loginMin;
        this.loginSec = loginSec;
        this.logoutHour = logoutHour;
        this.logoutMin = logoutMin;
        this.logoutSec = logoutSec;
    }

    // Each second event
    private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        now = DateTime.Now;
        //OnSecond();

        //login
        if (now.Hour == loginHour && now.Minute == loginMin && now.Second == loginSec)
            OnLoginTime();

        //logout
        if (now.Hour == logoutHour && now.Minute == logoutMin && now.Second == logoutSec)
            OnLogoutTime();
    }
}

}

Upvotes: 0

Views: 872

Answers (3)

Fredrik Enqvist
Fredrik Enqvist

Reputation: 47

This solution is probably only valid in my case. OP can delete this question if it´s believed to be a duplicate.

The first objective with was an easy to develop/run/debug situation with a GUI. Setting properties causes no cross thread exception. Showing the properties in a MessageBox.Show() causes no exception either. Hence no cross thread issues to dodge in the development/GUI stage.

The second objective was to migrate to a dll, hence no need to interfere with a GUI thread.

/Thanks anyway

Upvotes: 0

user11316976
user11316976

Reputation:

When you want to update View's control from another Thread it must show you error. Because it is using by UI Thread. In this case you have to use SynchronizationContext class or you can send Delegate to the App.Current.Dispatcher.BeginInvoke(delegate must be here);

SynchronizationContext _context = SynchronizationContext.Current;

private void UpdateLabels()
{
        _context.Post(x=> 
             {
                lblAccount.Text = AccountBalance.ToString();
             },null),

    //...
}

Alternative of SynchronizationContext :

private void UpdateLabels()
{
      var action = new Action(() => 
      {
              lblAccount.Text = AccountBalance.ToString();
      });

      App.Current.Dispatcher.BeginInvoke(action);

    //...
}

Both of them are same.

UI thread adapted for keyboard event and mouse event. When you App.Current.Dispatcher.BeginInvoke(delegate) you say to the UI Thread that "execute this too".

In addition you can suppose UI Thread like this

while(!thisApplication.Ended)
{
    wait for something to appear  in message queue
    Got something : what kind of this message? 
    Keyboard/Mouse message --> fire event handler 
    User BeginInvoke message --> execute delegate
    User Invoke message --> execute delegate & post result

}

Upvotes: 1

behzad sayyarpour
behzad sayyarpour

Reputation: 32

this error is beacuse of change lable text beacuse lable is in another thread you can use this code

 lblAccount.Invoke(new EventHandler((s, ee) => { lblAccount.Text = AccountBalance.ToString(); }));

Upvotes: 0

Related Questions