user7220101
user7220101

Reputation:

WPF background-method to open window

I am currently working on a application that checks emails from an email-account via IMAP. This function is called every 5 seconds and it needs some time to work through.

    private void CheckForRequests()
    {
        List<string[]> mails = CollectAllMails();

        for (int i = 0; i < mails.Count; i++)
        {
            if (mails[i][0].Split('_')[0] == "request")
            {
                //INVITATION TO ME
                if (mails[i][0].Split('_')[2] == username && mails[i][0].Split('_')[3] == "request")
                {
                    DeleteMail(mails[i][0]);
                    MessageBoxResult result = MessageBox.Show("Do you accept the request from " + mails[i][0].Split('_')[1], "Invitation", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
                    if (result == MessageBoxResult.Yes)
                    {
                        DeleteMail("request_" + mails[i][0].Split('_')[1] + "_" + mails[i][0].Split('_')[2] + "_" + mails[i][0].Split('_')[3]);
                        SendMail("request_" + mails[i][0].Split('_')[1] + "_" + mails[i][0].Split('_')[2] + "_accept", "");

                        ChatWindow chat = new ChatWindow();
                        chat.ShowDialog();
                        //do open chat window
                    }
                    else if (result == MessageBoxResult.No)
                    {
                        DeleteMail("request_" + mails[i][0].Split('_')[1] + mails[i][0].Split('_')[2]);
                        SendMail("request_" + mails[i][0].Split('_')[1] + "_" + mails[i][0].Split('_')[2] + "_decline", "");
                    }
                }
                //ACCEPTION FROM ANOTHER DUDE
                else if (mails[i][0].Split('_')[2] != username && mails[i][0].Split('_')[3] == "accept")
                {
                    ChatWindow chat = new ChatWindow();
                    chat.ShowDialog();
                }
                //REJECTION FROM ANOTHER DUDE
                else if (mails[i][0].Split('_')[2] != username && mails[i][0].Split('_')[3] == "decline")
                {
                    MessageBox.Show("Your invitation was declined.", "Sorry", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                }

            }
            else if (mails[i][0].Split('_')[0] == "somethingelse")
            {

            }

        }
    }

My loop calls this method every 5 seconds and in this time I can't write or do anything in my application. Im pretty sure that I have to use a Thread or Task to solve the problem but I didn't found out how to do this related to my case. When I call the method in a Task and I click Yes it crashes and says something like it has to be a STA-Thread... In this case I don't even want to access the GUI with the thread, I just want to check the mails and if the method found something it should do something like break from the Task and call a method (NOT from the Task).

What would be the cleanest solution for this problem?

Upvotes: 0

Views: 952

Answers (2)

MikeT
MikeT

Reputation: 5500

Your are Right about needing to use Threads

While @Gareth is right about the need to use a dispatcher to correctly access elements across theads, i actually don't see any threading in your code, though the error message clearly proves you have attempted some.

to implement the threading you have various options

firstly you can do this directly via Tasks or the older Thread classes

this would simply be done like so

private void CheckForRequestsAsync()=> Task.Run(()=>CheckForRequests());

this will instantly create and start a task that will perform CheckForRequests in a separate thread freeing the GUI to continues its work on the GUI, however this is a very basic implementation and likely will need further enhancement before reliably meeting your needs

another option is to take advantage of some of the newer features in .Net and use the async keyword

if you declare CheckForRequests as private async void CheckForRequests (object sender, EventArgs e) then the void will be automatically be converted into a task which can be fired off by an event hander as a async task say of a Timer

eg

  Timer timer = new Timer(5000);
  timer.Elapsed += CheckForRequests; //where CheckForRequests has the async keyword
  timer.Start();

combine this with the dispatcher information @Gareth suggested on anything that throws a cross thread access exception and you should be ready

this would look something like this:

MessageBoxResult result = Dispatcher.Invoke(() => 
    MessageBox.Show("Do you accept the request from " + mails[i][0].Split('_')[1], "Invitation", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
);

Note if you use async with out using the await keyword then you will get warnings that your thread may exit before any worker threads have completed, however this is just a warning as if you aren't calling any worker threads inside your method or you don't need them to complete before exiting then there is no harm done

Finally one of the comments suggested using DispatcherTimer rather than a Timer, i would not suggest this as every time the timer ticks, it will run your code in the GUI thread locking it just as you are already seen, the DispatcherTimer is best used when the timer heavily changes the GUI and is quick running

though if you redefined your code then you could user a DispatcherTimer by breaking out the gui element from the slow running process

dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += (s,e)=>{
   if( MessageBox.Show("Do you accept the request", "Invitation", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes) == MessageBoxResult.Yes)
   {
       Task.Run(()=>CheckForRequests());
   }
}
dispatcherTimer.Interval = new TimeSpan(0,0,1);
dispatcherTimer.Start();

Upvotes: 0

Gareth
Gareth

Reputation: 911

Your threading issue is caused by you trying to do UI stuff on a non-UI thread. You can solve this problem by using Dispatcher.Invoke whenever you call UI stuff like this

Application.Current.Dispatcher.Invoke(() => 
{
    // Your stuff here
});

So in your case you'd have something like this

void CheckForRequests()
{
    // Do stuff

    Application.Current.Dispatcher.Invoke(() => 
    {
        // Open your message box
    });

    // Do more stuff

    Application.Current.Dispatcher.Invoke(() => 
    {
        // Open another message box
    });
}

Upvotes: 1

Related Questions