ASP.NET WebForms で非同期処理を行う (.net4.5)
WebPageではasync/awaitをそのまま使うことができません。
利用するには以下の手順を踏みます。
1,Web.config設定
/configuration/appSettingsに、key=UseTaskFriendlySynchronizationContextな項目を追加。
詳細は下記を参照。
<configuration> .... <appSettings> <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" /> .... </appSettings> .... </configuration>
2,aspxファイルのページディレクティブ設定
末尾の「Async="true"」を追加する。コード例は下記を参照。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm.aspx.cs" Inherits="Sample.WebForm" Async="true" %>
3,RegisterAsyncTaskメソッドに非同期タスク登録
Page.RegisterAsyncTask()に非同期なタスクを登録する。コード例は下記を参照。
登録タスクを実行するには、Page.ExecuteRegisteredAsyncTasks()を明示的に呼び出す必要あり。
未実行タスクはPreRenderイベントとPreRenderCompleteイベントの間で実行される。
protected override void OnLoad(EventArgs e) { .... RegisterAsyncTask(new PageAsyncTask(async () => { var httpClient = new System.Net.Http.HttpClient(); var res = await httpClient.GetAsync("http://localhost/Hogege"); .... })); .... }
新しい非同期呼び出し(async,wait)を試す
.NET4.5から導入される新しい非同期呼び出し。
こんな感じで使うようです。
public partial class Form1 : Form { public Form1() { InitializeComponent(); } //内部で非同期処理を行うメソッドにはasyncをつける private async void button1_Click(object sender, EventArgs e) { //戻り値を返す非同期処理の実行。awaitをつけて非同期処理であることを明示する。 textbox1.Text = await HeaveyProcess(); //voidな非同期処理の実行 StopWatch(5); } private async Task<string> HeaveyProcess() { await Task.Delay(TimeSpan.FromSeconds(3.0)); return "重い処理が完了しました!!"; } private async void StopWatch(int sec) { Console.WriteLine("開始:{0}", DateTime.Now); await Task.Delay(TimeSpan.FromSeconds(sec * 1.0F)); Console.WriteLine("終了:{0}", DateTime.Now); } }
参考サイト
複雑なラムダ式
最近、ASP.NETMVCのソースコードを読んでいるのだが、そこでは複雑なラムダ式が多々でてくる。
今回はその一例としてアクションフィルタの処理(ControllerActionInvokerクラスのInvokeActionMethodWithFiltersメソッド)を自分なりに解釈し、コードを起こしたものを載せておく。
いくつか有用そうなテクニックを盗めたし、
ASP.NET MVCのソースコードは本当に勉強になります。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace LinqSample { public interface IActionParameter { object GetParameter(); } public class GenericParameter<T> : IActionParameter { private T val; public GenericParameter(T val) { this.val = val; } public object GetParameter() { return val; } } public class ActionResult { public object Value { get; set; } } public class ActionContext { public ActionResult CurrentValue { get; set; } public ActionContext() { CurrentValue = new ActionResult() { Value = "" }; } } public static class Action { public static ActionResult ConcatString(Func<ActionResult> action, IActionParameter param) { ActionResult current = action(); var newValue = current.Value.ToString() + param.GetParameter().ToString(); current.Value = newValue; Console.WriteLine(current.Value); return current; } public static ActionResult GetMaxValue(Func<ActionResult> action, IActionParameter param) { int tpResult; if (!int.TryParse(param.GetParameter().ToString(), out tpResult)) throw new ArgumentException("パラメーター不正(param)"); var current = action(); if (!int.TryParse(current.Value.ToString(), out tpResult)) throw new ArgumentException("パラメーター不正(action)"); var newValue = ""; if (Convert.ToInt32(current.Value) >= Convert.ToInt32(param.GetParameter())) newValue = current.Value.ToString(); else newValue = param.GetParameter().ToString(); Console.WriteLine("現在値:{0}、比較値:{1} => {2}を適用", current.Value, param.GetParameter(), newValue); current.Value = newValue; return current; } } public class ActionProcesser { public void DoAction(Func<ActionResult> acumrator, Func<Func<ActionResult>, IActionParameter, Func<ActionResult>> func) { var parameters = new List<IActionParameter>(); parameters.Add(new GenericParameter<int>(10)); parameters.Add(new GenericParameter<int>(20)); parameters.Add(new GenericParameter<int>(30)); parameters.Add(new GenericParameter<int>(40)); parameters.Add(new GenericParameter<int>(50)); parameters.Add(new GenericParameter<int>(60)); parameters.Add(new GenericParameter<int>(70)); parameters.Add(new GenericParameter<int>(80)); parameters.Add(new GenericParameter<int>(90)); parameters.Add(new GenericParameter<int>(100)); /* * 第1引数には、アキュムレーター値を返すFuncデリゲートを指定。 * 第2引数(アキュムレーター関数)には、アキュムレーターと同タイプのFuncを返すデリゲートを指定。 * こうすることで各パラメーターを外出しすることが可能になる。 * * アキュムレーター関数にデリゲートを渡すと即時実行されないようなので、最後に()をつけることでInvokeしている。 */ parameters.Aggregate(acumrator, (a, p) => func(a, p))(); } public void DoConcatString() { /* * アキュムレーターが保持する値をアキュムレーター自身が保持できない(デリゲートだから) * そこで、値保持用のContextクラスを使用することで対応した。 * */ var context = new ActionContext(); Func<ActionResult> acumrator = () => { return context.CurrentValue; }; /* * 実際の処理呼び出し。 * ラムダ式の中で、アキュムレーター関数の実処理を呼んでいる。 * こうすることで実処理のテスト容易になる。 * (これが一番の狙いか?) */ DoAction(acumrator, (a, p) => () => Action.ConcatString(a, p)); } public void DoCheckMaxValue() { var context = new ActionContext(); Func<ActionResult> acumrator = () => { context.CurrentValue.Value = "0"; return context.CurrentValue; }; DoAction(acumrator, (a, p) => () => Action.GetMaxValue(a, p)); } } }
Action.ConcatStringのテストコード。
[TestClass()] public class ActionTest { /// <summary> ///ConcatString のテスト ///</summary> [TestMethod()] public void ConcatStringTest() { Func<ActionResult> action = () => new ActionResult() { Value = "やまだ" }; IActionParameter param = new GenericParameter<string>("たろう"); ActionResult expected = new ActionResult() { Value = "やまだたろう" }; ActionResult actual; actual = LinqSample.Action.ConcatString(action, param); Assert.AreEqual(expected.Value, actual.Value); } /// <summary> ///GetMaxValue のテスト ///</summary> [TestMethod()] public void GetMaxValueTest() { Func<ActionResult> action = () => new ActionResult() { Value = "100" }; IActionParameter param = new GenericParameter<int>(99); ActionResult expected = new ActionResult() { Value = "100" }; ActionResult actual; actual = LinqSample.Action.GetMaxValue(action, param); Assert.AreEqual(expected.Value, actual.Value); } }
ジェネリックな各種リストをスレッドセーフに扱う
その1
System.Collections.ICollectionインターフェイスにキャストすることで、SyncRootにアクセス可能。これでlockを行うことができる。
//こんな感じ var list = new List<String>(); lock (((System.Collections.ICollection)list).SyncRoot) { list.Add("アイテム1"); }
Visual Studio 2010でアプリケーションのパフォーマンス・チューニング
DBアプリケーションのパフォーマンス・チューニング − @ITの内容メモ
クエリのデータ転送量を減らす
不要な項目をSELECT区に含めない。安易に*を使用しない。
SQLServerからの転送量がかなり節約できる。
クエリの内容をデバッグ出力
Entity Frameworkでは、ObjectQueryクラスのToTraceStringメソッドを使用する。
var q = from a in db.Tweets select a;
var trace = ((System.Data.Objects.ObjectQuery)q).ToTraceString();
大量のデータをインポートする方法
テストデータを作成して、それをテーブルにInsertする場合、現実的には2方法が考えられる。
-
- ManagementStudioのオブジェクトエクスプローラから対象テーブルを右クリックし、「上位200行の編集」を選択。編集可能状態でテーブルデータが表示されるので追加データを貼り付ける。
- 「データのインポートおよびエクスポート 」ウィザードを使用してデータをインポートする。
普段は手軽なので1方法でインポートしているが、速度が遅いので大量データをインポートする必要がある場合は、2方法を利用すべき。
IObservableインターフェイス
IObservable(T) インターフェイス (System)
IObserver(T) インターフェイス (System)
.NET Framework 4から追加されたこれらのインターフェイスを使ったサンプルを作成していました。
オブザーバーパターンは一方通行の通知を行うパターンだというのに、チャットアプリケーションを作ってしまったため
クライアントウィンドウがごちゃごちゃしています。
配信側、受信側クラス
public class ChatHost : IObservable<ChatMessage> { protected List<IObserver<ChatMessage>> observers = new List<IObserver<ChatMessage>>(); public ChatHost() { } public IDisposable Subscribe(IObserver<ChatMessage> observer) { observers.Add(observer); return new _ChatMessage(this, observer); } private class _ChatMessage : IDisposable { private ChatHost parent; private IObserver<ChatMessage> me; public _ChatMessage(ChatHost parent, IObserver<ChatMessage> me) { this.parent = parent; this.me = me; } public void Dispose() { if (parent != null && parent.observers.Contains(me)) parent.observers.Remove(me); } } public void AddMessage(ChatMessage msg) { if (msg == null) { foreach (var o in observers) { o.OnError(new ChatException() { Msg = new ChatMessage() { From = "SYSTEM", Message = "無言メッセージが送信されました。送ったのは誰だ!!", SendAt = DateTime.Now } }); } return; } foreach (var o in observers) { o.OnNext(msg); } } public void EndChat() { foreach (var o in observers) { o.OnNext(new ChatMessage() { From = "SYSTEM", Message = "今日はお開きです!!", SendAt = DateTime.Now }); } int obsCount = observers.Count; for (int i = obsCount - 1; i >= 0; i--) { observers[i].OnCompleted(); } } } public class ChatException : Exception { public ChatMessage Msg { get; set; } public ChatException() : base() { } } public class ChatClient : IObserver<ChatMessage> { private IDisposable unsubscriber; private ObservableCollection<ChatMessage> msgItemList; public ChatClient(ObservableCollection<ChatMessage> msgItemList) { this.msgItemList = msgItemList; } public virtual void Subscribe(IObservable<ChatMessage> provider) { if (provider != null) unsubscriber = provider.Subscribe(this); } public void OnCompleted() { if(unsubscriber != null) unsubscriber.Dispose(); } public void OnError(Exception error) { var charError = error as ChatException; if (charError == null) return; msgItemList.Add(charError.Msg); } public void OnNext(ChatMessage value) { msgItemList.Add(new ChatMessage() { From = value.From, Message = value.Message, SendAt = value.SendAt }); } }
メインウィンドウ。クライアント画面表示やチャットセッションの切断をおこなう。
<Window x:Class="ObservableSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <StackPanel> <Button x:Name="btnAddClient" Click="btnAddClient_Click" Margin="10">クライアント追加</Button> <Button x:Name="btnEnd" Margin="10" Click="btnEnd_Click">終了</Button> </StackPanel> </Window>
public partial class MainWindow : Window { private ChatHost provider = new ChatHost(); public MainWindow() { InitializeComponent(); } private void btnAddClient_Click(object sender, RoutedEventArgs e) { var window = new Window1(provider); window.Show(); } private void btnEnd_Click(object sender, RoutedEventArgs e) { provider.EndChat(); } }
クライアントウィンドウ。
<Window x:Class="ObservableSample.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="356" Width="518" Loaded="Window_Loaded" Closed="Window_Closed"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="8*" /> <RowDefinition Height="2*" /> </Grid.RowDefinitions> <ListView Grid.Row="0" x:Name="listview1"> <ListView.View> <GridView> <GridViewColumn DisplayMemberBinding="{Binding Path=From}" Header="送信者" Width="100" /> <GridViewColumn DisplayMemberBinding="{Binding Path=Message}" Header="メッセージ" Width="150" /> <GridViewColumn DisplayMemberBinding="{Binding Path=SendAt}" Header="日付" Width="200" /> </GridView> </ListView.View> </ListView> <StackPanel Orientation="Horizontal" Grid.Row="1"> <TextBox x:Name="textbox1" Width="300" Margin="5"></TextBox> <Button x:Name="button1" Width="100" Margin="5" Click="button1_Click">送信</Button> </StackPanel> </Grid> </Window>
public partial class Window1 : Window { private ObservableCollection<ChatMessage> msgs = new ObservableCollection<ChatMessage>(); private ChatHost provider; private ChatClient observer; private string userName = ""; public Window1() { InitializeComponent(); } public Window1(ChatHost provider) { this.provider = provider; this.observer = new ChatClient(msgs); observer.Subscribe(provider); InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { listview1.ItemsSource = msgs; //ユーザー名入力ダイアログを表示、ユーザー名設定。 var dialog = new Window2(); dialog.Owner = this; dialog.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterOwner; dialog.ShowInTaskbar = false; dialog.ShowDialog(); this.userName = dialog.UserName; this.Title = userName; } private void button1_Click(object sender, RoutedEventArgs e) { if (textbox1.Text.Trim().Length == 0) { provider.AddMessage(null); } else { provider.AddMessage(new ChatMessage() { From = userName, Message = textbox1.Text, SendAt = DateTime.Now }); } } private void Window_Closed(object sender, EventArgs e) { observer.OnCompleted(); } }
匿名メソッドの変数キャプチャ
基本的な大事なことです。
Javascriptのクロージャと同じ考え方です。。。
ど忘れしてましたので再確認を兼ねて書き置きします。
//非同期でFriendGridにイメージを追加していく (ダメバージョン) Task.Factory.StartNew(() => { foreach (var friend in ViewModel.Friends) { //ループで一意な変数がキャプチャされるので、全て末尾要素が選択されてしまう。 Dispatcher.BeginInvoke(new Action(() => { FriendGrid.Children.Add(new Image(){DataContext = friend}); }) , System.Windows.Threading.DispatcherPriority.Background, null ); } });
//非同期でFriendGridにイメージを追加していく (OKバージョン) Task.Factory.StartNew(() => { foreach (var friend in ViewModel.Friends) { var f = friend; //ループ内にローカル変数を追加。匿名メソッドがこの変数をキャプチャするようにする。 Dispatcher.BeginInvoke(new Action(() => { FriendGrid.Children.Add(new Image(){DataContext = f}); }) , System.Windows.Threading.DispatcherPriority.Background, null ); } });