IDLE - EndCheckForUpdates hangs

0 votes
asked Jan 15, 2012 by PatrikE (220 points)
edited Dec 13, 2013

I'm trying to use the IMAP IDLE feature to detect new mail. When a new mail is detected, the IDLE should stop and I will download the new mail.

However, when I call EndCheckForUpdates from the Notification event handler, it's just hanging. See example code below:

The class have these members:

Imap client; IAsyncResult async = null;

The client is properly connected to the server, and the inbox is selected. Then I do:

client.Notification += new ImapNotificationEventHandler(client_Notification); async = client.BeginCheckForUpdates(15*60000, CallBack, null);

This method is being called when a new mail arrives:

void client_Notification(object sender, ImapNotificationEventArgs e) { client.Abort(); // Tried both with and without this one if (async != null) client.EndCheckForUpdates(async); }

The problem is that the EndCheckForUpdates method call doesn't finish. How do I solve this? I want to cancel the IDLE as soon as I get a mail notification.

Applies to: Rebex Secure Mail

6 Answers

+2 votes
answered Jan 16, 2012 by Lukas Matyska (54,470 points)
edited Jan 16, 2012
 
Best answer

Please note the Notification event is blocking. It means the calling thread continues when the client_Notification method returns.

In your case you made a deadlock. EndCheckForUpdates cannot finish, because it is waiting for the Notification event to be finished which is waiting for the EndCheckForUpdates to be finished.

The correct usage is as follows:

client.Notification += new ImapNotificationEventHandler(client_Notification);
client.BeginCheckForUpdates(15*60000, CheckForUpdatesCallback, null);

void client_Notification(object sender, ImapNotificationEventArgs e)
{
    // abort IDLE state
    client.Abort();
}

void CheckForUpdatesCallback(IAsyncResult ar)
{
    // end the IDLE operation
    client.EndCheckForUpdates(ar);
}
0 votes
answered Dec 10, 2013 by dirk (530 points)
edited Dec 10, 2013

Hello Lukáš,

I have a question regarding the way you described to use IMAPIdle. Unfortunately the rebex library I use neither offers a BeginCheckForUpdates nor a EndCheckForUpdates method. The only method I can use is CheckForUpdates. So I gave it a try this way:

Thread thread = new Thread(new ThreadStart(WorkThreadFunction));
thread.Start();

public void WorkThreadFunction()
{
   client.Notification += new ImapNotificationEventHandler(client_Notification);
   client.BeginCheckForUpdates(15*60000);
}

void client_Notification(object sender, ImapNotificationEventArgs e)
{
   // abort IDLE state
   client.Abort();
   //read mails
   readMails();
}
void readMails()
 {
   LINE 3000:   ImapMessageCollection list =   
                client.GetMessageList(ImapListFields.FullHeaders);
    [...]
 }

When I call the function in line 3000 I get the following exception: "Another operation is pending"

What am I doing wrong? When I checked the object client I found out that the state is "Reading" before I try the command GetMessageList..Is that correct or do I handle everything differently with the thread, et cetera.

Thanks, dirk

commented Dec 10, 2013 by Lukas Matyska (54,470 points)
edited Dec 10, 2013

I am a little bit confused. You wrote, that the assembly doesn't offer BeginCheckForUpdates method, but in your code you have client.BeginCheckForUpdates(15*60000);.

Please note, that the BeginCheckForUpdates method is visible in assembly for .NET 2.0 (located in $INSTALL_DIR$\bin\net-2.0).

If you are using assembly for .NET 4.0 (located in $INSTALL_DIR$\bin\net-4.0) add using Rebex.Legacy; to see the BeginCheckForUpdates method.

0 votes
answered Dec 10, 2013 by dirk (530 points)
edited Dec 10, 2013

Hello Lukáš,

thanks a lot, it does work now! :) Great... And that despite of my typing error above, I meant CheckForUpdates without the "Begin" in my code, sorry. After using Rebex.Legacy I could finally use it, thanks.

I just have two other questions: 1. Should I put your BeginCheckForConnection into a thread? And what way would be best? Becauase I got another pending message somewhere along the way (but it didn't disturb) 2. How can I restart the BeginCheckForUpdate again? The application wants to wait for mails all the time, so what's the best way to do that? I tried to add the BeginCheckForUpdates after the EndCheckForUpdates to start the checking process again, but that didn't work out.

Thanks for your great help, dirk

0 votes
answered Dec 11, 2013 by dirk (530 points)
edited Dec 11, 2013

Hello,

good news, I got it working continually :)

void CheckForUpdatesCallback(IAsyncResult ar)
{
    // end the IDLE operation
    client.EndCheckForUpdates(ar);
    readMails();
}

And then I call the BeginCheckForUpdates method again after I read and deleted the new mail from the server. My mistake was that I did it too early and ran into an infinite loop.

Thanks again for the help.

p.s.: Just out of curiousity: How could this be solved with tasks?

dirk

+1 vote
answered Dec 11, 2013 by Lukas Matyska (54,470 points)
edited Dec 13, 2013

This could be solved with tasks using await in .NET 4.5 really easy:

Imap imap;
bool checkingUpdates;

private async void ProcessUpdates()
{
    // register notification handler
    imap.Notification += imap_Notification;

    while (true)
    {
        try
        {
            // set flag
            checkingUpdates = true;

            // wait for updates 15 minutes asynchronously
            await imap.CheckForUpdatesAsync(15 * 60 * 1000);
        }
        finally
        {
            // reset flag
            checkingUpdates = false;
        }

        // process new messages
        readMails();
    }
}

void imap_Notification(object sender, ImapNotificationEventArgs e)
{
    // abort only if client is currently waiting for updates
    if (checkingUpdates)
        imap.Abort();
}
commented Dec 11, 2013 by dirk (530 points)
edited Dec 11, 2013

Hello Lukáš,

oh great, that's a clever solution, thanks a lot! I didn't work with 'await' yet, but as far as I understand the finally case and the readMails() function will only be reached after the imap.Abort() in the imap_Notification call, right? That way it will check for mails when a new mail was received, if I understand correctly.

Thanks, dirk

commented Dec 11, 2013 by Lukas Matyska (54,470 points)
edited Dec 11, 2013

The finally case and the readMails method are reached after the CheckForUpdatesAsync completes, which can arise when imap.Abort() is called or after 15 minutes (this is value passed to the CheckForUpdatesAsync).

If you are not using .NET 4.5 leave a comment. I will show you how to do the same without await.

commented Dec 12, 2013 by dirk (530 points)
edited Dec 13, 2013

Good morning Lukáš,

first of all, thanks for all your efforts. Ah I see, thanks for the explanation. I actually use .NET 4.0...so if you could show me that example above without await, that would be awesome :) Thanks very much.

dirk

+1 vote
answered Dec 12, 2013 by Lukas Matyska (54,470 points)
edited Dec 13, 2013

Equivalent code without await can look like this:

Imap imap;
bool checkingUpdates;

private void Start()
{
    imap = new Imap();
    imap.Connect(...);
    imap.Login(...);

    // register notification handler
    imap.Notification += imap_Notification;

    // start processing
    ProcessUpdates();
}

private void ProcessUpdates()
{
    // set flag
    checkingUpdates = true;

    // wait for updates 15 minutes asynchronously
    var t = imap.CheckForUpdatesAsync(15 * 60 * 1000);

    // when task is finished continue with CheckForUpdatesFinished method 
    // using current SyncContext is useful in WinForm apps, 
    // because you can update WinForm controls without cross-thread exception
    t.ContinueWith(CheckForUpdatesFinished, TaskScheduler.FromCurrentSynchronizationContext());
}

private void CheckForUpdatesFinished(Task finishedTask)
{
    // reset flag
    checkingUpdates = false;

    if (finishedTask.IsFaulted)
        // log exception and quit processing
        return;

    // process new messages
    readMails();

    // restart processing
    ProcessUpdates();
}

void imap_Notification(object sender, ImapNotificationEventArgs e)
{
    // abort only if client is currently waiting for updates
    if (checkingUpdates)
        imap.Abort();
}

The only difference is processing of unhandled exceptions in the ContinueWith action. When using await unhandled exceptions are displayed with standard dialog. Without await unhandled exceptions are lost.

commented Dec 13, 2013 by dirk (530 points)
edited Dec 13, 2013

Good morning Lukáš,

thanks so much, what a great example! I just read your code thoroughly, and it is well understandable and nicely commented so that I can follow fluently even if I don't have much experience with tasks. The hint with the SyncContext is also very useful, great.

Thanks again and I wish you a nice weekend, dirk

...