1



I am trying my best with mastering Threading in C# and in WPF particularly.

Background of the problem:
I have a MVVM application, which have a long authentication process (few seconds long). I want to keep the UI responsible (keep app window dragable and resizable, as well - showing preloader with a gif element). To achieve that - I have separated all the UI elements and DB retrievals. After that - I have implemented the A background worker into the Login View.

Here is the code of the Login View:

    private void ValidateLogin()
    {
        Usuario user = new Usuario();
        MainWindow mw = Window.GetWindow(this) as MainWindow;
        mw.preloaderShow();

        BackgroundWorker bw = new BackgroundWorker();

        bw.DoWork += (o, args) =>
        {   
            user = _viewmodel.Login(tbLogin.Text, tbPassword.Password); //TimeConsuming DataRetrieval from DB
        };
        bw.RunWorkerCompleted += (o, args) =>
        {   
            if (user != null)
            {
                if (user.Activo == 0)
                {
                    mw.preloaderHide();
                    CustomMessageBox WrongLoginMessage = new CustomMessageBox("El usuario esta inactivo.");
                    WrongLoginMessage.ShowDialog();
                }
                else
                {
                    AppSession.Instance.SetValue("currentuser", user);
                    btnProceed.Visibility = Visibility.Visible;
                    mw.preloaderHide();
                }
            }
        };

        bw.RunWorkerAsync();
    }

Problem:
Obviously I am falling into a deadlock, on the bw.DoWork, because "The calling thread cannot access this object because a different thread owns it.".

Questions:
1. Since I am not updating any UI in the bw.DoWork() (lets consider this as a fact at this moment), - why is the Background worker busy? I mean - as I understood, that the whole conception was introduced to run processes in a separated Thread with as less pain as possible?
2. How to retrieve the User from DB while keeping the UI responsive? Maybe BackgroundWorker is not the best concept for achieving this goal (Task / TaskFactory)?

Will be very appreciated with helping me on this one. Thank you in advance.

Egor Osaulenko
  • 87
  • 1
  • 12
  • I can simply gues about the reason of downvote. – Egor Osaulenko Nov 02 '16 at 19:21
  • 1
    You really should avoid such old and heavy object as `BackgroundWorker` in WPF solution – VMAtm Nov 03 '16 at 05:23
  • @VMAtm I can understand what you trying to say, but it would be way more useful if you would say what solution do you recommend (Task?). – Egor Osaulenko Nov 03 '16 at 19:29
  • The question received several downvotes already. It may be not the best solution - yes. But the question is more than reasonable, described pretty solid and raised pretty often. I don`t really care about the rep points, but it decreases the chances to help others. – Egor Osaulenko Nov 03 '16 at 19:37
  • I suggest `async` event handler with it's ability to execute code **after** long operation on the UI – VMAtm Nov 03 '16 at 20:09
  • @VMAtm I execute event async in BackgroundWorker.DoWork and execute UI operations BackgroundWorker.RunWorkerCompleted **after** long calculation. If using `async` I will have to handle dedicated Thread manualy, which (in this particular situation) would not give me any cheese. Which difference am I missing (seriously)? – Egor Osaulenko Nov 03 '16 at 20:35
  • 1
    Why do you will need to handle the dedicated thread? `async` operations are done in Thread pool, you do not control the threads. And `BackgroundWorker` is heavy object, you should test both approaches to see the difference in speed. – VMAtm Nov 03 '16 at 20:41
  • @VMAtm that is a reasonable explaination. Thank you. – Egor Osaulenko Nov 03 '16 at 20:48
  • 1
    http://stephencleary.com/ - good place to start with `async` reading. Good luck with your projects – VMAtm Nov 03 '16 at 21:00

2 Answers2

2

Simple thumb rule is you cannot access UI element from background thread. You are accessing textbox and password control in DoWork which results in an exception.

Fetch user name and password on UI thread and you are good.

string userName = tbLogin.Text;
string password = tbPassword.Password;
bw.DoWork += (o, args) =>
{   
   user = _viewmodel.Login(userName, password); 
};
Rohit Vats
  • 79,502
  • 12
  • 161
  • 185
  • 1
    My friend - that is the solution. What I`ve learned - you cant not just Update UI, but interact with the UI from the BackgrounWorker. Thank you. – Egor Osaulenko Nov 02 '16 at 19:22
1
private async void ValidateLogin()
{
    MainWindow mw = Window.GetWindow(this) as MainWindow;
    mw.preloaderShow();

    Task<Usuario> taskLogin = Login(tbLogin.Text, tbPassword.Password);
    await taskLogin;

    Usuario user = taskLogin.Result;

    if (user != null)
    {
        if (user.Activo == 0)
        {
            mw.preloaderHide();
            CustomMessageBox WrongLoginMessage = new CustomMessageBox("El usuario esta inactivo.");
            WrongLoginMessage.ShowDialog();
        }
        else
        {
            AppSession.Instance.SetValue("currentuser", user);
            btnProceed.Visibility = Visibility.Visible;
            mw.preloaderHide();
        }
    }
}

public Task<Usuario> Login(string loginText, string password)
{
    return Task.Run(() =>
    {
        return new Usuario();
    });
}

Use Task instead of backgroundworker.

remarkies
  • 113
  • 10
  • What would be the significant benefit of using Task instead? (I am serious - does it work faster / lighter) – Egor Osaulenko Nov 03 '16 at 19:31
  • More information [here](http://stackoverflow.com/questions/3513432/task-parallel-library-replacement-for-backgroundworker). – remarkies Nov 04 '16 at 07:48
  • 1
    I don't like the way you have to write the backgroundworker (Syntax). The code looks much cleaner when you use a Task. There aren't big antvantages using task. It uses newer API and handels asynchron procedures better. If its faster or not i don't know. – remarkies Nov 04 '16 at 07:54