پشتیبانی از چندنخی و استخر ارتباطات در مشتریان ایمیل

کلاینت‌های ایمیل مانند ImapClient, Pop3Client، و SmtpClient قابل استفاده در محیط چندنخی است. یک کلاینت می‌تواند یک یا چند اتصال با سرور داشته باشد. برای مدیریت مجموعهٔ اتصالات داخل کلاینت، از استخر اتصال استفاده می‌شود. تعداد اتصالاتی که می‌توانند همزمان ساخته و استفاده شوند توسط CredentialsByHostClient.MaxConnectionsPerServer خاصیت. این خاصیت می‌تواند ۱ یا مقدار بزرگتری باشد. به‌صورت پیش‌فرض برابر با ۱۰ است.

یک صف دستورات برای هر اتصال پیاده‌سازی شده است تا عملیات‌های چندنخی را پشتیبانی کند. دستورات ساده‌ای که در پروتکل تعریف شده‌اند را پیاده‌سازی می‌کنند، مانند Noop, Authenticateو غیره. کاربر ممکن است فرمان‌های بیشتری نسبت به تعداد اتصالات موجود اجرا کند، اما این فرمان‌ها تنها زمانی اجرا می‌شوند که کلاینت بتواند برای عملیات یک اتصال جدید ایجاد کند.

چگونگی رفتار کلاینت‌های ایمیل در محیط چندنخی

کلاینت‌های ایمیل رفتارهای زیر را دارند:

  1. هنگامی که MaxConnectionsPerServer = 1، کلاینت یک اتصال می‌سازد و احراز هویت و مجوزدهی را انجام می‌دهد. این اتصال تا زمان آزادسازی کلاینت در حالت کاری باقی می‌ماند. تمام عملیات‌ها از نخ‌های مختلف به یک صف دستورات در اتصال اصلی هدایت می‌شوند.

  2. هنگامی که MaxConnectionsPerServer > 1، کلاینت تعداد مورد نیاز اتصالات را می‌سازد و برای هر اتصال احراز هویت و مجوزدهی انجام می‌دهد. یک اتصال به‌عنوان اتصال اصلی رزرو می‌شود. این اتصال تا زمان آزادسازی کلاینت در حالت کاری باقی می‌ماند. سایر اتصالات در صورت نیاز ساخته و آزاد می‌شوند. حداکثر تعداد چنین اتصالاتی توسط MaxConnectionsPerServer خاصیت. به عنوان مثال، اگر MaxConnectionsPerServer = 2، سپس یک اتصال به‌عنوان اتصال اصلی رزرو می‌شود و یک اتصال دوم به‌عنوان اتصال اضافی برای عملیات اجراشده در نخ‌های دیگر استفاده می‌شود. بنابراین اگر MaxConnectionsPerServer = 3، سپس اولین اتصال به‌عنوان اتصال اصلی رزرو می‌شود و دو اتصال دیگر به‌عنوان اتصالات اضافی برای عملیات اجراشده در نخ‌های دیگر استفاده می‌شوند. وقتی درخواست یک اتصال از نخ جدیدی می‌آید و همه اتصالات در حال استفاده هستند، کلاینت تا کاهش تعداد اتصالات استفاده‌شده صبر می‌کند. این لحظه بسیار مهم است که چرا آزادسازی صحیح اتصالات این‌قدر مهم است.

مثال‌های استفاده از کلاینت‌های ایمیل در محیط چندنخی

کاربر ممکن است عملیات را در نخ‌های مختلف به چندین روش انجام دهد. این روش‌ها به دو دسته تقسیم می‌شوند.

استفاده از متدهای ناهمگام (Begin/End)

کاربر از متدهای ناهمگام (Begin/End) تعریف‌شده در کلاینت استفاده می‌کند. در این حالت، کلاینت ایمیل وقتی نیاز باشد نخ‌های جدیدی را راه‌اندازی می‌کند. یک صف وظیفه در کلاینت پیاده‌سازی شده است (با صف دستورات در اتصال اشتباه گرفته نشود). یک وظیفه می‌تواند اجرا شود اگر اتصال موجود باشد. وقتی تعداد اتصالات استفاده‌شده کمتر از مقدار حد بالا شد، کلاینت یک اتصال جدید می‌سازد، یک نخ برای وظیفه جاری ایجاد می‌کند و این وظیفه را اجرا می‌کند. مثال استفاده از عملیات ناهمگام:

// Create an imapclient with host, user and password
ImapClient client = new ImapClient();
client.Host = "domain.com";
client.Username = "username";
client.Password = "password";
client.SelectFolder("InBox");

ImapMessageInfoCollection messages = client.ListMessages();
IAsyncResult res1 = client.BeginFetchMessage(messages[0].UniqueId);
IAsyncResult res2 = client.BeginFetchMessage(messages[1].UniqueId);
MailMessage msg1 = client.EndFetchMessage(res1);
MailMessage msg2 = client.EndFetchMessage(res2);

استفاده از نخ‌های ساخته‌شده توسط کاربر

کاربر ممکن است نخ‌ها را با اشیائی مانند Thread, ThreadPool, Task، یا هر شیء دیگری که برای این منظور در نظر گرفته شده است. کاربر ممکن است از نخ‌های ایجاد شده در کدهای شخص ثالث نیز استفاده کند. در این صورت، کلاینت دو مدل رفتار دارد.

a. اگر کاربر مراقب ایجاد اتصالات اضافی برای عملیات در نخ نباشد، تمام عملیات‌های آن نخ به صف دستورات اتصال اصلی فرستاده می‌شوند. مثال زیر عملیاتی در یک نخ اضافی بدون ایجاد اتصال جدید را نشان می‌دهد — تمام تراکنش‌ها از طریق اتصال اصلی انجام می‌شوند:

List<MailMessage> List = new List<MailMessage>();
ThreadPool.QueueUserWorkItem(delegate(object o)
{
    client.SelectFolder("folderName");
    ImapMessageInfoCollection messageInfoCol = client.ListMessages();
    foreach (ImapMessageInfo messageInfo in messageInfoCol)
    {
        List.Add(client.FetchMessage(messageInfo.UniqueId));
    }
});

b. وقتی کاربر متدی را برای ایجاد اتصال جدید برای یک نخ اضافی اجرا می‌کند، این نخ تا زمانی که مقدار سهمیه برای اتصال‌های جدید تغییر کند و اجازه اتصال جدید داده شود مسدود می‌ماند. سپس اتصال جدید ساخته می‌شود. این اتصال به‌عنوان اتصال پیش‌فرض برای تمام عملیات‌ها در این نخ تنظیم می‌شود. پس از اتمام تمام عملیات‌ها در این نخ، باید اتصال آزاد شود. برای ایجاد اتصال‌های جدید، از CredentialsByHostClient.CreateConnection متد. این متد یک شیء بازمی‌گرداند که پیاده‌سازی می‌کند IDisposable رابط است. برای آزاد کردن اتصال، Dispose متد باید فراخوانی شود. ایجاد و آزاد کردن یک اتصال باید داخل نخ‌ای که عملیات ایمیل در آن انجام می‌شود، انجام شود. تلاش برای ایجاد یک اتصال جدید در نخ‌ای که کلاینت ایمیل در آن ساخته شده است منجر به خطا می‌شود، زیرا در آن لحظه این نخ نمی‌تواند برای ایجاد اتصال جدید استفاده شود. همچنین امکان ایجاد اتصال جدید زمانی که MaxConnectionsPerServer = 1. مثال کد برای ایجاد یک اتصال جدید در یک نخ اضافی:

List<MailMessage> List1 = new List<MailMessage>();
ThreadPool.QueueUserWorkItem(delegate(object o)
{
    using (IDisposable connection = client.CreateConnection())
    {
        client.SelectFolder("FolderName");
        ImapMessageInfoCollection messageInfoCol = client.ListMessages();
        foreach (ImapMessageInfo messageInfo in messageInfoCol)
            List1.Add(client.FetchMessage(messageInfo.UniqueId));
    }
});

استخر اتصال

از نسخه Aspose.Email 19.3 به‌بعد، استخر اتصال بازسازی شد. EmailClient کلاسی معرفی شد که در نهایت جایگزین CredentialsByHostClient کلاس. EmailClient کلاس یک ConnectionAsgmtMode ویژگی که حالت تخصیص اتصال را در محیط چندرشته‌ای تعریف می‌کند. EmailClient.ConnectionAsgmtMode با استفاده از ConnectionAsgmtType شمارش‌گر.

انواع اتصال

سه نوع اتصال وجود دارد:

  • اتصال اصلی. این اتصال همراه با کلاینت ایمیل ساخته و آزاد می‌شود. نمی‌توان آن را به‌صورت دستی ساخت یا آزاد کرد.
  • اتصال پیش‌فرض. کاربر می‌تواند برای نخ‌ها اتصال‌های پیش‌فرض را با CreateConnection متد ساخته شوند. اگر یک اتصال پیش‌فرض وجود داشته باشد، تمام متدهای کلاینت ایمیل که در یک نخ اجرا می‌شوند به‌صورت ضمنی از این اتصال استفاده می‌کنند. تنها یک اتصال پیش‌فرض می‌تواند در هر نخ وجود داشته باشد. می‌تواند به‌صورت دستی یا خودکار ساخته شود، بسته به EmailClient.ConnectionAsgmtMode خاصیت. این اتصالات می‌توانند به‌صورت دستی با EmailClient.CreateConnection(createAsDefaultConnection = true) متد ساخته شوند. اگر اتصال پیش‌فرض استفاده نشود (بسته به حالت تخصیص اتصال)، به‌جای آن اتصال اصلی به‌صورت ضمنی استفاده می‌شود.
  • اتصالات مستقل. این‌ها اتصالی هستند که به نخ‌ها لینک ندارند. می‌توانند به‌صورت دستی ساخته شوند و باید به‌صورت صریح به‌عنوان پارامتر متد استفاده شوند. این اتصالات می‌توانند به‌صورت دستی با EmailClient.CreateConnection() متد یا EmailClient.CreateConnection(createAsDefaultConnection = false) متد.

انواع تخصیص اتصال

برای تنظیم EmailClient.ConnectionAsgmtMode خاصیت، ConnectionAsgmtType از این شمارش استفاده می‌کند. انواع تخصیص ارائه شده در زیر فهرست شده‌اند.

  • ConnectionAsgmtType.UseMainOrDefault این حالت به‌صورت پیش‌فرض در کلاینت‌های ایمیل استفاده می‌شود. کلاینت ایمیل برای تمام عملیات‌ها از اتصال اصلی استفاده می‌کند اگر اتصال پیش‌فرضی ایجاد نشده باشد یا اگر اتصال به‌صورت صریح به‌عنوان پارامتر متد پاس داده نشده باشد. اتصال اصلی همزمان با کلاینت ایمیل ساخته می‌شود. کاربر می‌تواند با CreateConnection متد استفاده کند. اگر یک اتصال پیش‌فرض برای یک نخ ایجاد شود، به‌صورت ضمنی برای تمام متدهای کلاینت ایمیل که در آن نخ فراخوانی می‌شوند استفاده می‌شود. اگر اتصال پیش‌فرض برای نخ ایجاد نشود، اتصال اصلی برای تمام متدهای فراخوانی‌شده در آن نخ استفاده می‌شود. کاربر همچنین می‌تواند اتصال‌هایی که به نخ‌ها لینک ندارند (اتصال‌های پیش‌فرض نیستند) را با CreateConnection متد. برای استفاده از اتصال‌های دیگر (نه اصلی نه پیش‌فرض)، کاربر باید اتصال را به‌صورت صریح به‌عنوان پارامتر متد پاس بدهد. کاربر می‌تواند به‌علاوه تعداد دلخواهی اتصال ایجاد کند. تنها یک اتصال پیش‌فرض می‌تواند در هر نخ وجود داشته باشد. لطفاً توجه داشته باشید که اتصال‌های پیش‌فرض به‌درستی کار می‌کنند اگر کاربر از Thread اشیاء برای برنامه‌نویسی چندوظیفه‌ای. اگر کاربر از استخر ارتباطات یا Task اشیاء برای برنامه‌نویسی چندوظیفه‌ای، این حالت ممکن است منجر به رفتار نادرست شود. برای جلوگیری از این مشکل، کاربر باید به‌صورت دستی اتصال پیش‌فرض (در صورت استفاده) را در پایان اجرای کد آزاد کند.

  • ConnectionAsgmtType.UseMain کلاینت ایمیل برای تمام عملیات‌ها از اتصال اصلی استفاده می‌کند. اتصال اصلی همزمان با کلاینت ایمیل ساخته می‌شود. کاربر نمی‌تواند اتصالات پیش‌فرض ایجاد کند، اما می‌تواند اتصال‌های بدون لینک به نخ‌ها را با CreateConnection متد، اتصالات پیش‌فرض برای نخ‌ها ایجاد کند. برای استفاده از اتصال‌های دیگر، کاربر باید آنها را به‌صورت صریح به‌عنوان پارامتر متد پاس بدهد.

  • ConnectionAsgmtType.UseDefault کلاینت ایمیل برای تمام عملیات‌ها از چندین نخ فقط از اتصالات پیش‌فرض به‌صورت ضمنی استفاده می‌کند. اتصال اصلی در این حالت استفاده نمی‌شود. اگر برای یک نخ اتصال پیش‌فرضی ایجاد نشده باشد (در اولین فراخوانی متد کلاینت ایمیل)، کلاینت ایمیل به‌صورت ضمنی برای آن نخ یک اتصال پیش‌فرض می‌سازد قبل از اجرای اولین عملیات. کاربر نمی‌تواند با CreateConnection متد استفاده کند زیرا به‌صورت خودکار ایجاد می‌شوند. کاربر همچنین می‌تواند اتصال‌هایی که به نخ‌ها لینک ندارند با CreateConnection متد استفاده کند. برای استفاده از اتصال‌های دیگر، کاربر باید آنها را به‌صورت صریح به‌عنوان پارامتر متد پاس بدهد. کاربر می‌تواند به‌علاوه تعداد دلخواهی اتصال ایجاد کند. تنها یک اتصال پیش‌فرض می‌تواند در هر نخ استفاده شود. لطفاً توجه داشته باشید که اتصال‌های پیش‌فرض به‌درستی کار می‌کنند اگر کاربر از Thread اشیاء برای برنامه‌نویسی چندوظیفه‌ای. اگر کاربر از استخر ارتباطات یا Task اشیاء برای برنامه‌نویسی چندوظیفه‌ای، این حالت ممکن است منجر به رفتار نادرست شود. برای جلوگیری از این مشکل، کاربر باید به‌صورت دستی اتصال پیش‌فرض را در پایان اجرای کد آزاد کند.

پیشنهادات

اگر کاربر تمام دستورات را به اتصال اصلی بفرستد، ممکن است وضعیتی پیش آید که دستورات از نخ‌های مختلف مخلوط شوند. کاربر باید بفهمد کدام دستورات به ترتیبشان وابسته‌اند و اقدامات همگام‌سازی را انجام دهد. همچنین لازم است امکان اجرای دستورات در نشست‌های متفاوت (IMAP/POP3) را در نظر بگیرد. زمان‌برترین عملیات‌ها عبارتند از FetchMessage, AppendMessage، و Send. احتمالاً منطقی است که این عملیات‌ها را با یک نخ جدید و یک اتصال جدید انجام دهید. عملیات‌های سریع مانند Delete منطقی است که این کار را با اتصال اصلی انجام دهید. لطفاً توجه داشته باشید که مقداردهی اولیه یک اتصال جدید عملیاتی نسبتاً زمان‌بر است.