3

3 つの DataGrid がある小さな Silverlight アプリケーションをプログラムしたいと考えています。各 Datagrid は、非同期メソッドを使用して Web サービスからデータを取得します。ここで、最初のデータ グリッドで Web サービスからデータを取得し、最初のデータ グリッドで選択した行のパラメーターを使用して 2 番目のデータ グリッドを取得し、最初の 2 つのデータ グリッドのパラメーターを使用して 3 番目のデータ グリッドを取得します。最初のデータグリッドは、イベント ハンドラーを登録し、非同期メソッドを使用して、MainPage メソッドでデータを取得します。

ここで私の問題は、SelectionChanged (イベント処理) メソッドで他のデータグリッドに非同期メソッドを使用していることです。データグリッド 2 で何かを選択してデータグリッド 1 に戻ると、すべてのデータグリッドが消えるため、この概念は間違っていると思います。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ServiceModel;
using Rebat.SymptomeService;


namespace Rebat
{
    public partial class MainPage : UserControl
    {
        ServiceClient client = new ServiceClient();

        public MainPage()
        {
            InitializeComponent();
            ServiceClient client = new ServiceClient();
           client.SymptomeListCompleted += new EventHandler<SymptomeListCompletedEventArgs>(client_SymptomeListCompleted);
            client.SymptomeListAsync();
        }


        void client_SymptomeListCompleted(object sender, SymptomeListCompletedEventArgs e)
        {
            CustomerGrid.ItemsSource = e.Result;
        }
        void client_CustomerListCompleted(object sender, CustomerListCompletedEventArgs e)
        {
           CustomerGrid2.ItemsSource = e.Result;
        }
        void client_SalzListCompleted(object sender, SalzListCompletedEventArgs e)
        {
            SalzGrid.ItemsSource = e.Result;
        }

        private void CustomerGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            Symptome sympt = CustomerGrid.SelectedItem as Symptome;
            client.CustomerListCompleted += new EventHandler<CustomerListCompletedEventArgs>(client_CustomerListCompleted);
            client.CustomerListAsync(sympt.sId.ToString());

        }

        private void CustomerGrid2_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            Symptome2 sympt2 = CustomerGrid2.SelectedItem as Symptome2;
            client.SalzListCompleted += new EventHandler<SalzListCompletedEventArgs>(client_SalzListCompleted);
            //this is the PROBLEM:
            client.SalzListAsync(sympt2.sy1.ToString(), sympt2.sy2.ToString());
        }

    }
}

何を変更する必要がありますか、または非同期メソッドをどのように使用する必要がありますか? イベント処理メソッド内で非同期メソッドを使用できますか? これは、Web サービスを使用する使用法に適用されますか?

4

1 に答える 1

0

.NET 4.5 非同期機能を使用できると仮定すると、サービス プロキシをタスク ベースの非同期パターンに適合させると、その種の呼び出しを行うのがはるかに簡単になります。イベント ハンドラーで async メソッドを使用できます。新しい async/await キーワードを十分に活用するには、イベント ハンドラーを非同期にするだけで済みます。

プロキシを適応させるには、まずヘルパー静的クラスを宣言します。

public static partial class TAPExtensions
{
    public static void HandleCompletion<T>(TaskCompletionSource<T> tcs, AsyncCompletedEventArgs args, Func<T> getResult, Action unregisterHandler = null)
    {
        // Transfers the results from the AsyncCompletedEventArgs and getResult() to the 
        // TaskCompletionSource, but only AsyncCompletedEventArg's UserState matches the TCS 
        // (this check is important if the same WebClient is used for multiple, asynchronous 
        // operations concurrently).  Also unregisters the handler to avoid a leak. 
        if (args.UserState == tcs)
        {
            if (args.Cancelled) tcs.TrySetCanceled();
            else if (args.Error != null) tcs.TrySetException(args.Error);
            else tcs.TrySetResult(getResult());
            if (unregisterHandler != null) unregisterHandler();
        }
    }
}

ここで、プロキシを拡張する別のヘルパー クラスを作成します。操作ごとにアダプター メソッドを作成します。

public static class ServiceClientEx
{
    public static async Task<IEnumerable<ReturnType>> SalzListTask(this ServiceClient proxy, string arg1, string arg2)
    {
        var tcs = new TaskCompletionSource<IEnumerable<ReturnType>>();
        EventHandler<SalzListCompletedEventArgs> handler = (s, args) => TAPExtensions.HandleCompletion(tcs, args, () => args.Result);
        proxy.SalzListCompleted += handler;

        try
        {
            proxy.SalzListAsync(arg1, arg2, tcs);
            return await tcs.Task;
        }
        finally { proxy.SalzListCompleted -= handler; }
    }
}

これで、コードで拡張メソッドを使用できるようになりました (ServiceClientEx クラスの名前空間に "using" ディレクティブを追加すると仮定します)。結果は次のようになります。

public partial class MainPage : UserControl
{
    ServiceClient client = new ServiceClient();

    public MainPage()
    {
        InitializeComponent();
        ServiceClient client = new ServiceClient();
    }


    private async void CustomerGrid2_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        Symptome2 sympt2 = CustomerGrid2.SelectedItem as Symptome2;

        alzGrid.ItemsSource = await client.SalzListTask(sympt2.sy1.ToString(), sympt2.sy2.ToString());
    }
}

イベントハンドラ宣言での「async」キーワードの使用に注意してください。非同期動作をメソッドに伝達します。選択が変更されると、ハンドラーは最初の await に到達するまで正常に実行され、元の呼び出し元 (コントロールのディスパッチャー) に戻ります。呼び出しが終了すると、継続 (itemssource を操作結果に設定) が UIThread で実行されます。

次のように、呼び出しをラップして例外をチェックすることもできます。

private async void CustomerGrid2_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        try{
            Symptome2 sympt2 = CustomerGrid2.SelectedItem as Symptome2;

            alzGrid.ItemsSource = await client.SalzListTask(sympt2.sy1.ToString(), sympt2.sy2.ToString());
        }catch(MyException ex){
            MessageBox.Show(ex.ToString());
        }
    }

いくつかの提案:

  • コンストラクターから非同期呼び出しを実行することは良い考えではないと思います。たとえば、コントロールの Loaded イベントで何らかの初期化を行った後に起動することができます (MVVM パターンに従って、ViewModel でこの種の操作を実行し、UI からそれを消費するだけのアプローチがより適切ですが、それは別のトピックです)。 .
  • 現在行っている方法では、選択が変更されるたびに新しいハンドラーがプロキシに追加されるため、ハンドラーは必要以上に実行されます。
  • プロキシに障害が発生するとインスタンスが使用できなくなるため、呼び出しを行うたびにプロキシをインスタンス化するようにしてください。
于 2012-11-14T17:41:11.247 に答える