我正在使用here
中找到的IMAP空闲示例代码该示例需要一个Console.ReadKey来取消CancellationTokenSource,但建议只要该事件可以访问CancellationTokenSource,就可以在新邮件到达时在CountChanged事件中将其取消。
如何在CountChanged事件中访问CancellationTokenSource?
这是上面链接的代码摘要...
// Keep track of changes to the number of messages in the folder (this is how we'll tell if new messages have arrived).
client.Inbox.CountChanged += (sender, e) => {
// Note: the CountChanged event will fire when new messages arrive in the folder and/or when messages are expunged.
var folder = (ImapFolder)sender;
Console.WriteLine("The number of messages in {0} has changed.", folder);
// Note: because we are keeping track of the MessageExpunged event and updating our
// 'messages' list, we know that if we get a CountChanged event and folder.Count is
// larger than messages.Count, then it means that new messages have arrived.
if (folder.Count > messages.Count) {
Console.WriteLine("{0} new messages have arrived.", folder.Count - messages.Count);
// Note: your first instict may be to fetch these new messages now, but you cannot do
// that in an event handler (the ImapFolder is not re-entrant).
//
// If this code had access to the 'done' CancellationTokenSource (see below), it could
// cancel that to cause the IDLE loop to end.
// HOW DO I DO THIS??
}
};
Console.WriteLine("Hit any key to end the IDLE loop.");
using (var done = new CancellationTokenSource()) {
// Note: when the 'done' CancellationTokenSource is cancelled, it ends to IDLE loop.
var thread = new Thread(IdleLoop);
thread.Start(new IdleState(client, done.Token));
Console.ReadKey();
done.Cancel();
thread.Join();
}
答案 0 :(得分:2)
您需要做的就是稍微重新排列代码,以便事件处理程序可以访问done
令牌。
以下是您如何执行此操作的示例:
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using MailKit;
using MailKit.Net.Imap;
using MailKit.Security;
namespace ImapIdle
{
class Program
{
// Connection-related properties
public const SecureSocketOptions SslOptions = SecureSocketOptions.Auto;
public const string Host = "imap.gmail.com";
public const int Port = 993;
// Authentication-related properties
public const string Username = "username@gmail.com";
public const string Password = "password";
public static void Main (string[] args)
{
using (var client = new IdleClient ()) {
Console.WriteLine ("Hit any key to end the demo.");
var idleTask = client.RunAsync ();
Task.Run (() => {
Console.ReadKey (true);
}).Wait ();
client.Exit ();
idleTask.GetAwaiter ().GetResult ();
}
}
}
class IdleClient : IDisposable
{
List<IMessageSummary> messages;
CancellationTokenSource cancel;
CancellationTokenSource done;
bool messagesArrived;
ImapClient client;
public IdleClient ()
{
client = new ImapClient (new ProtocolLogger (Console.OpenStandardError ()));
messages = new List<IMessageSummary> ();
cancel = new CancellationTokenSource ();
}
async Task ReconnectAsync ()
{
if (!client.IsConnected)
await client.ConnectAsync (Program.Host, Program.Port, Program.SslOptions, cancel.Token);
if (!client.IsAuthenticated) {
await client.AuthenticateAsync (Program.Username, Program.Password, cancel.Token);
await client.Inbox.OpenAsync (FolderAccess.ReadOnly, cancel.Token);
}
}
async Task FetchMessageSummariesAsync (bool print)
{
IList<IMessageSummary> fetched;
do {
try {
// fetch summary information for messages that we don't already have
int startIndex = messages.Count;
fetched = client.Inbox.Fetch (startIndex, -1, MessageSummaryItems.Full | MessageSummaryItems.UniqueId, cancel.Token);
break;
} catch (ImapProtocolException) {
// protocol exceptions often result in the client getting disconnected
await ReconnectAsync ();
} catch (IOException) {
// I/O exceptions always result in the client getting disconnected
await ReconnectAsync ();
}
} while (true);
foreach (var message in fetched) {
if (print)
Console.WriteLine ("{0}: new message: {1}", client.Inbox, message.Envelope.Subject);
messages.Add (message);
}
}
async Task WaitForNewMessagesAsync ()
{
do {
try {
if (client.Capabilities.HasFlag (ImapCapabilities.Idle)) {
// Note: IMAP servers are only supposed to drop the connection after 30 minutes, so normally
// we'd IDLE for a max of, say, ~29 minutes... but GMail seems to drop idle connections after
// about 10 minutes, so we'll only idle for 9 minutes.
using (done = new CancellationTokenSource (new TimeSpan (0, 9, 0))) {
using (var linked = CancellationTokenSource.CreateLinkedTokenSource (cancel.Token, done.Token)) {
await client.IdleAsync (linked.Token);
// throw OperationCanceledException if the cancel token has been canceled.
cancel.Token.ThrowIfCancellationRequested ();
}
}
} else {
// Note: we don't want to spam the IMAP server with NOOP commands, so lets wait a minute
// between each NOOP command.
await Task.Delay (new TimeSpan (0, 1, 0), cancel.Token);
await client.NoOpAsync (cancel.Token);
}
break;
} catch (ImapProtocolException) {
// protocol exceptions often result in the client getting disconnected
await ReconnectAsync ();
} catch (IOException) {
// I/O exceptions always result in the client getting disconnected
await ReconnectAsync ();
}
} while (true);
}
async Task IdleAsync ()
{
do {
try {
await WaitForNewMessagesAsync ();
if (messagesArrived) {
await FetchMessageSummariesAsync (true);
messagesArrived = false;
}
} catch (OperationCanceledException) {
break;
}
} while (!cancel.IsCancellationRequested);
}
public async Task RunAsync ()
{
// connect to the IMAP server and get our initial list of messages
try {
await ReconnectAsync ();
await FetchMessageSummariesAsync (false);
} catch (OperationCanceledException) {
await client.DisconnectAsync (true);
return;
}
// keep track of changes to the number of messages in the folder (this is how we'll tell if new messages have arrived).
client.Inbox.CountChanged += OnCountChanged;
// keep track of messages being expunged so that when the CountChanged event fires, we can tell if it's
// because new messages have arrived vs messages being removed (or some combination of the two).
client.Inbox.MessageExpunged += OnMessageExpunged;
// keep track of flag changes
client.Inbox.MessageFlagsChanged += OnMessageFlagsChanged;
await IdleAsync ();
client.Inbox.MessageFlagsChanged -= OnMessageFlagsChanged;
client.Inbox.MessageExpunged -= OnMessageExpunged;
client.Inbox.CountChanged -= OnCountChanged;
await client.DisconnectAsync (true);
}
// Note: the CountChanged event will fire when new messages arrive in the folder and/or when messages are expunged.
void OnCountChanged (object sender, EventArgs e)
{
var folder = (ImapFolder) sender;
// Note: because we are keeping track of the MessageExpunged event and updating our
// 'messages' list, we know that if we get a CountChanged event and folder.Count is
// larger than messages.Count, then it means that new messages have arrived.
if (folder.Count > messages.Count) {
int arrived = folder.Count - messages.Count;
if (arrived > 1)
Console.WriteLine ("\t{0} new messages have arrived.", arrived);
else
Console.WriteLine ("\t1 new message has arrived.");
// Note: your first instict may be to fetch these new messages now, but you cannot do
// that in this event handler (the ImapFolder is not re-entrant).
//
// Instead, cancel the `done` token and update our state so that we know new messages
// have arrived. We'll fetch the summaries for these new messages later...
messagesArrived = true;
done?.Cancel ();
}
}
void OnMessageExpunged (object sender, MessageEventArgs e)
{
var folder = (ImapFolder) sender;
if (e.Index < messages.Count) {
var message = messages[e.Index];
Console.WriteLine ("{0}: message #{1} has been expunged: {2}", folder, e.Index, message.Envelope.Subject);
// Note: If you are keeping a local cache of message information
// (e.g. MessageSummary data) for the folder, then you'll need
// to remove the message at e.Index.
messages.RemoveAt (e.Index);
} else {
Console.WriteLine ("{0}: message #{1} has been expunged.", folder, e.Index);
}
}
void OnMessageFlagsChanged (object sender, MessageFlagsChangedEventArgs e)
{
var folder = (ImapFolder) sender;
Console.WriteLine ("{0}: flags have changed for message #{1} ({2}).", folder, e.Index, e.Flags);
}
public void Exit ()
{
cancel.Cancel ();
}
public void Dispose ()
{
client.Dispose ();
cancel.Dispose ();
done?.Dispose ();
}
}
}