2012-12-20 98 views
5

我試圖瞭解什麼是正確的代碼來異步獲取一組數據,當我沒有訪問客戶端庫我正在使用檢索數據。我指定了一個端點和一個日期範圍,我應該檢索一個播放列表列表。在Start()調用之後,我現在所擁有的東西永遠不會回來。注意:這是在WinForm中運行的。我正在嘗試更好地理解任務,並且不只是想跳到等待或BackgroundWorker。我知道我在某個地方迷路了。C#和任務 - 用戶界面線程掛起 - 預異步/等待關鍵字

private void GoButtonClick(object sender, EventArgs e) 
    { 
     string baseUrl = "http://someserver/api"; 
     var startDateTime = this._startDateTimePicker.Value; 
     var endDateTime = this._endDateTimePicker.Value; 
     _getPlaylistsFunc = delegate() 
      { 
       var client = new PlaylistExportClient(baseUrl); 
       return client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList(); 
      }; 
     var task = new Task<List<Playlist>>(_getPlaylistsFunc); 
     task.ContinueWith((t) => DisplayPlaylists(t.Result)); 
     task.Start(); 
    } 

    private void DisplayPlaylists(List<Playlist> playlists) 
    { 
     _queueDataGridView.DataSource = playlists; 
    } 

UPDATE 我做了這些改變,但現在的應用似乎掛起UI線程。

private void GoButtonClick(object sender, EventArgs e) 
    { 
     string baseUrl = "http://someserver/api"; 
     var startDateTime = this._startDateTimePicker.Value; 
     var endDateTime = this._endDateTimePicker.Value; 
     var token = Task.Factory.CancellationToken; 

     var context = TaskScheduler.FromCurrentSynchronizationContext(); 
     Task.Factory.StartNew(() => 
      { 
       var client = new PlaylistExportClient(baseUrl); 
       _queueDataGridView.DataSource = client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList(); 

      },token,TaskCreationOptions.None,context); 

    } 
+0

如果你在'GetPlaylistsByDateRange'中放置了一個斷點,你會發現它實際上被調用了嗎? – user7116

+2

您需要將同步上下文傳遞給continuation,以便它在UI線程中運行,而不是在另一個線程池線程中運行。除此之外,我沒有看到任何真正的錯誤。 – Servy

+2

您的更新在UI線程上運行* all *任務。只有第二個應該在那裏運行。 –

回答

2

它看起來像你分配給後臺線程中的UI控件的屬性。這通常是壞消息。當你這樣做時,WPF通常會拋出一個異常,不確定WinForms。

捕獲後臺線程中的數據,但在將其分配給UI控件之前切換回主UI線程。嘗試使用類似

var uiSync = SynchronizationContext.Current; 
    Task.Factory.StartNew(() => 
     { 
      var client = new PlaylistExportClient(baseUrl); 
      var list = client.GetPlaylistsByDateRange(...).ToList(); 
      uiSync.Post(() => _queueDataGridView.DataSource = list, null); 
     },token,TaskCreationOptions.None,context); 
+0

嗯。我明白 - 我想。 .Post()不是我的上下文中的方法。 – BuddyJoe

+0

Woops。好的,你調用一個上下文變量實際上是一個調度器。我將用代碼更新我的答案以獲取同步上下文 – dthorpe

+0

看起來像是非常接近謝謝。我只是在制定Post()lambda參數。現在IDE正在報告匿名函數的不正確簽名。 – BuddyJoe

3

將數據發佈到UI線程我建議您使用基於任務的異步模式。這很簡單:

private async void GoButtonClick(object sender, EventArgs e) 
{ 
    string baseUrl = "http://someserver/api"; 
    var startDateTime = this._startDateTimePicker.Value; 
    var endDateTime = this._endDateTimePicker.Value; 
    var playlists = await Task.Run(() => 
    { 
     var client = new PlaylistExportClient(baseUrl); 
     return client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList(); 
    }); 
    _queueDataGridView.DataSource = playlists; 
} 

請注意,這將阻塞線程池線程;如果您可以修改庫以使其具有GetPlaylistsByDateRangeAsync方法,則可以使其更高效。

編輯:如果你被困在.NET 4.0中,你可以安裝Microsoft.Bcl.Async得到充分async/await能力。如果 - 對於一些無法解釋的原因 - 你仍然不能使用async/await,那麼你可以做這樣的:

private void GoButtonClick(object sender, EventArgs e) 
{ 
    string baseUrl = "http://someserver/api"; 
    var startDateTime = this._startDateTimePicker.Value; 
    var endDateTime = this._endDateTimePicker.Value; 
    var context = TaskScheduler.FromCurrentSynchronizationContext(); 
    Task.Run(() => 
    { 
     var client = new PlaylistExportClient(baseUrl); 
     return client.GetPlaylistsByDateRange(startDateTime, endDateTime).ToList(); 
    }).ContinueWith(t => 
    { 
     _queueDataGridView.DataSource = t.Result; 
    }, context); 
} 

但是請注意,你的錯誤處理這種方法更加複雜。

+0

我一直在試圖學習如何在異步/等待之前完成這個任務,但也有很好的信息。 – BuddyJoe

+0

我根據問題中指定的'async-await'標記給出了這個答案,如果你想要一個'async'前答案,'Task.ContinueWith'(加上'TaskScheduler.FromCurrentSynchronizationContext')或'BackgroundWorker'會「async」代碼比這兩個選項中的任何一個都要乾淨得多 –

+0

使用'ContinueWith'方法查看代碼的更新答案 –