استفاده از Async/Await درWPF جهت فراخوانی سرویس هایWCF

پنجشنبه 26 آذر 1394

طراحی اپلیکیشن های WPF و WCF باید کاربر پسند بوده و حتی در حالی که مشغول پردازش است باید responsive یا واکنش گرا باقی بماند. در این مقاله می خواهیم از قابلیت های async و await در NET framework 4.5. استفاده کنیم بنابراین می توان از استفاده ی background worker برای پیاده سازی ناهمزمان(asynchrony) جلوگیری کنیم، که به جای آن از وب سرویس ASMX استفاده می شود.

استفاده از Async/Await درWPF جهت فراخوانی سرویس هایWCF

مقدمه

اگر شما با Silverlight برنامه نویسی کرده باشید، متوجه شده اید که دسترسی به خدمات وب ذاتا در Silverlight ناهمزمان است. به عبارت دیگر در WPF مدل پیش فرض همزمان است. در حالت همزمان، در حالیکه round-trip برای سرور جهت واکشی داده ها ایجاد شده است، UI شما قفل باقی خواهد ماند و این ممکن است به کاربر شما این تصور را بدهد که اپلیکیشن قطع شده است. برخی کاربران ممکن است پنجره را باز نگه داشته و چندین بار کلیک کنند تا نهایتا پاسخ اپلیکیشن ایجاد شود. برخی هم ممکن است با Ctrl+Alt+Delete به end-task رفته وبرنامه را ببندند.

خوشبختانه، Net Framework 4.5. دو کلمه کلیدی async و await را معرفی کرد که به طرز جادویی یک متد را به ناهمزمان تبدیل می کند. داخل بدنه این متد می توانید یک فرایند long-running (طولانی مدت) مانند یک وب سرویس را با قرار دادن یک پیشوند در کلمه کلیدی await فراخوانی کنید. هر خط کدی که بعد از این بیاید یک انتظار(wait) تا زمانی که اجرای عملگر ها تمام شود ایجاد خواهد کرد. در طول مدت انتظار، این کنترل به فراخواننده ی اصلی از متد async منتقل می شود. هنگامی که فرایند long-running (طولانی مدت) اجرایش کامل شود، بقیه خطوطی که بعد از متد await می آیند مقداری طول می کشد تا اجرای آنها کامل شود.

این مقاله برای کسانی است که این نوع وضعیت را در گذشته روی Winforms/WPF و با استفاده از کامپوننت background worker یا با استفاده از دیگر thread ها برنامه نویسی کرده باشند یا حتی با ترکیبی از متدهای Async تولید شده توسط پروکسی وب سرویس بیشتر رخدادهای عملیات به اتمام رسانیده باشند. این ویژگی معرفی شده در NET 4.5. جایگزین بهتری برای مدیریت فرایند رویدادها و   غیره می باشد و عملیات را در یک مکان( برای مثال رویداد کلیک یک دکمه) مدیریت می کند.

 در صورت تمایل میتوانید به صورت رایگان آموزش WCF به همراه پروژه عملی را در سایت تاپ لرن مشاهده کنید .

حالا می خواهیم یک وب سرویس WCF در دسترس را از WPF با busy indicator و ویژگی premature cancellation (لغو زودهنگام) بدون استفاده از delegate ها، background worker و یا ویژگی premature cancellation پیاده سازی کنیم.

استفاده از کد

مثالی که در اینجا نشان داده شده است دو پروژه دارد:

1. یک پروژه، سرویس WCF است که شامل یک متد (GetBooks) برای اطلاعات کتابها و نویسندگان آنها است.

2. پروژه یعدی یک اپلیکیشن کلاینت WPF است که یک سرویس را مصرف می کند. این اپلیکیشن WPF باید از نوع NET 4.5 باشد در غیر این صورت نمی توان از کلمه های کلیدی async/await استفاده کرد.

نیازمندی ها:

1. Visual Studio 2013

Extended WPF Kit .2 دانلود شده و اضافه شده به Toolbox

در اینجا به صورت مرحله به مرحله پروژه ضمیمه شده به این مقاله را توضیح می دهیم.

مرحله 1: ایجاد اپلیکیشن سرویس WCF:

Visual Studio را باز کرده و یک اپلیکیشن WCF ایجاد کنید و نام آن را MyService قرار دهید. در solution explorer، بر روی فایل IService1.cs راست کلیک کرده و نام آن را به IBookService.cs تغییر دهید. Visual Studio از شما می پرسد که  آیا تمام رفرنس ها را تغییر دهد یا خیر که شما Yes را انتخاب کنید. پس بر روی فایل Service1.svc راست کلیک کرده و نام آن را به BookService.svc تغییر دهید، که به این صورت است: بر روی کلمه Service1 راست کلیک کرده و Refactor را انتخاب کنید و نام آن را به BookService تغییر دهید. با این کار این اطمینان  ایجاد می شود که Visual Studio به درستی تمام رفرنس های service1 را به BookService تغییر نام داده است.

تمام کدهایی که داخل هردو IBookService.cs وجود دارد را حذف کنید یا تغییر  دهید و آن را به صورت زیر ایجاد کنید:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
 
namespace MyService
{
    [ServiceContract]
    public interface IBookService
    {
 
        [OperationContract]
        ObservableCollection<Book> GetBooks();
 
    }
 

    [DataContract]
    public class Book
    {
        [DataMember]
        public int BookId { get; set; }
 
        [DataMember]
        public string BookName { get; set; }
 
        [DataMember]
        public string Author { get; set; }
    }
}

حالا تمام کدهایی که داخل BookService.cs وجود دارند را نیز حذف کرده و یا تغییر دهید تا به صورت زیر ایجاد شود:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
 
namespace MyService
{
    public class BookService : IBookService
    {
 
        public ObservableCollection<Book> GetBooks()
        {
 
            //This lines simulates the lag in roundtrip to Server.
            System.Threading.Thread.Sleep(5000); //puts 5-second lag.

 
            //Create a list and convert it into an observable collection and return back to the client.
            return new ObservableCollection<Book>(
                new List<Book>
                {
                    new Book{ BookId = 1, BookName="Learning C#", Author = "Nejimon CR"},
                    new Book{ BookId = 2, BookName="Introuction to ADO.NET", Author = "Nejimon CR"},
                    new Book{ BookId = 3, BookName="Lambda Made Easy", Author = "Nejimon CR"},
                    new Book{ BookId = 4, BookName="Robinson Crusoe", Author = "Daniel Defoe"},
                    new Book{ BookId = 5, BookName="The White Tiger", Author = "Aravind Adiga"},
                    new Book{ BookId = 6, BookName="The God of Small Things", Author = "Arunthati Roy"},
                    new Book{ BookId = 7, BookName="Midnight's Children", Author = "Salman Rushdie"},
                    new Book{ BookId = 8, BookName="Hamlet", Author = "William Shakespeare"},
                    new Book{ BookId = 9, BookName="Paradise Lost", Author = "John Milton"},
                    new Book{ BookId = 10, BookName="Waiting for Godot", Author = "Samuel Beckett"},
                }
            );
        }
    }
}

چه چیزی را به صورت ضروری انجام داده ایم که یک interface را تعریف می کند، تعریف یک کلاس به عنوان داده قراردادی برای برگرداندن داده کتاب به کلاینت ضروری می باشد، و interface در کلاس سرویس پیاده سازی می شود. متد GetBooks نام چند کتاب را همراه با نویسندگان آنها برمی گرداند. البته در یک برنامه واقعی، ممکن است بخواهید داده را از بانک اطلاعاتی بازیابی کنید که این نیز امکان پذیر است.

همچنین خط زیر را هم در بالای صفحه قرار می دهیم:

System.Threading.Thread.Sleep(5000); 

این کار تاخیر آزمایش شده در زمان دسترسی به وب سرویس روی اینترنت را شبیه سازی می کند، بنابراین می توانیم استفاده از busy indicator و abort feature را نشان دهیم.

هنگامی که وب سرویس شما آماده باشد، آن را build کرده و اگر خطایی وجود داشته باشد در این زمان نشان داده می شود.

مرحله 2: ایجاد اپلیکیشن WPF Client

در مرحله بعدی، یک  پروژه WPF در solution ایجاد کرده(File => New Project) و نام آن را AsyncAwaitDemo قرار می دهیم. به یاد داشته باشید که target framework نسخهرا روی NET 4.5. قرار دهید. در solution explorer بر روی پروژه WPF راست کلیک کرده و آن را به عنوان Startup پروژه قرار دهید.

دوباره بر روی WPF راست کلیک کرده و گزینه Add Service Refeference را باز کنید. بر روی Advanced کلیک کرده و مطمئن شوید که Allow generation of asynchronous operations کنترل شده است.

سرویس مورد نظر را در solution خود پیدا کرده و فضای نام آن را  BookService قرار دهید و بر روی OK کلیک کنید. حالا یک رفرنس به BookService خود اضافه کنید.

Mainwindow.xaml را باز کرده و یک دکمه و یک Busy Indicator به آن اضافه کنید. لطفا برای قرار دادن صحیح کنترل ها به صفحه HTML مراجعه کنید:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" x:Class="AsyncAwaitDemo.MainWindow"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <xctk:BusyIndicator Name="busyIndicator">
            <xctk:BusyIndicator.BusyContent>
                <StackPanel>
                    <TextBlock HorizontalAlignment="Center">Please wait...</TextBlock>
                    <Button Content="Abort" Name="btnAbort" HorizontalAlignment="Center"/>
                </StackPanel>
            </xctk:BusyIndicator.BusyContent>
            
            <xctk:BusyIndicator.Content>
                <StackPanel>
                    <Button Content="Get Data" Name="btnGetData" HorizontalAlignment="Center"/>
                    <DataGrid Name="grdData" AutoGenerateColumns="True"/>
                </StackPanel>
            </xctk:BusyIndicator.Content>
 
        </xctk:BusyIndicator>
    </Grid>
</Window>

پنجره کد MainWindow.xaml را باز کرده(F7 را بزنید) و کد زیر را در آن جایگزین کنید:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using AsyncAwaitDemo.BookService;
 
namespace AsyncAwaitDemo
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
 
        //Create event procs.
          btnGetData.Click += btnGetData_Click;
          btnAbort.Click += btnAbort_Click;
          grdData.AutoGeneratingColumn += grdData_AutoGeneratingColumn;
        }
 
        //Service Proxy
        BookServiceClient client;
 

        //this will make sure that the "ExtentionData" column added as part of Serialization is prevented from showing up on the grid.
        void grdData_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            if (e.Column.Header.ToString() == "ExtensionData")
            {
                e.Cancel = true;
            }
        }
 
          
        //If the request is still being executed, this gives a chance to abort it.
        void btnAbort_Click(object sender, RoutedEventArgs e)
        {
            if (client != null)
            {
                if (client.State == System.ServiceModel.CommunicationState.Opened)
                {
                    client.Abort();    
                }
                
            }
        }
 
        //Async method to get the data from web service.
       async void btnGetData_Click(object sender, RoutedEventArgs e)
       {
           try
           {
               busyIndicator.IsBusy = true;
 
               client = new BookServiceClient();
 
               var result = await client.GetBooksAsync();
               
               client.Close();
 
               grdData.ItemsSource = result;
 
               busyIndicator.IsBusy = false;
 

           }
           catch (Exception ex)
           {
               busyIndicator.IsBusy = false;
 
               if (!ex.Message.Contains("The request was aborted: The request was canceled."))
               {
                   MessageBox.Show("Unexpected error: " + ex.Message, 
                       "Async Await Demo", MessageBoxButton.OK, MessageBoxImage.Error);              
 
               }
           }
 

           this.UpdateLayout();
       }
    }
}

تا اینجا کدنویسی کامل می شود. Solution را Build کنید. اگر همه چیز به خوبی انجام شده باشد، اپلیکیشن راه انداز شده و UI را نمایش می دهد. بر دکمه Get Data کلیک کرده و می توانید خروجی را به صورت زیر ببینید:

حالا بر روی Get Data کلیک کنید تا داده ها از سرویس مورد نظر دریافت شود:

فایل های ضمیمه

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان
  • WPF
  • 2k بازدید
  • 3 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید