在上一個(gè)任務(wù)成功給團(tuán)隊(duì)demo之后颁糟,大家同意了我進(jìn)一步的考慮——讓下載數(shù)據(jù)這個(gè)進(jìn)程可控,能夠根據(jù)用戶的需要關(guān)閉下載進(jìn)程,進(jìn)行其他任務(wù)坊谁。
我的想法來自于之前學(xué)Android中的AsyncTask:
AsyncTask enables proper and easy use of the UI thread. This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.
如定義所述搔耕,AsyncTask能夠讓我們在Android程序編寫中無需使用多線程隙袁,即可使后臺(tái)任務(wù)獨(dú)立運(yùn)行,并將結(jié)果反饋給UI進(jìn)程弃榨。
這里將老師的課件對比案例引用如下:
與一般的Sync相比為什么要使用Async呢菩收,相信大家看完上面的栗子已經(jīng)有所了解,我們再現(xiàn)來看看Microsoft給出的解釋鲸睛,注意一些關(guān)鍵詞:
Async Improves Responsiveness
Asynchrony is essential for activities that are potentially blocking, such as when your application accesses the web. Access to a web resource sometimes is slow or delayed. If such an activity is blocked within a synchronous process, the entire application must wait. In an asynchronous process, the application can continue with other work that doesn't depend on the web resource until the potentially blocking task finishes.
由于需要改進(jìn)的項(xiàng)目是C#編寫的Window Form Application娜饵,在網(wǎng)上找到,在.NET的Toolbox中官辈,有一個(gè)BackgroundWorker控件也能夠讓我們“proper and easy use of the UI thread"
BackgroundWorker makes threads easy to implement in Windows Forms. Intensive tasks need to be done on another thread so the UI does not freeze. It is necessary to post messages and update the user interface when the task is done.
是不是和AsyncTask的思想是一致的~~
BackgroundWorker通過DoWork箱舞,ProgressChanged遍坟,RunWorkerCompleted這三個(gè)EventHandler,分別控制程序的后臺(tái)運(yùn)行晴股,進(jìn)程的更新和結(jié)束后愿伴,我們只需要把任務(wù)分類到這三個(gè)EventHandler中,然后在UI線程中調(diào)用即可电湘。
為了控制在程序在后臺(tái)的運(yùn)行狀況和是否可被取消隔节,需要設(shè)置它的兩個(gè)屬性True/False:WorkerReportsProgress, WorkerSupportsCancellation胡桨。
關(guān)于BackgroundWorker就不贅述了官帘,在學(xué)習(xí)使用的時(shí)候,這兩篇教程相見恨晚:
BackgroundWorker作為Asynchronous Programming的基礎(chǔ)昧谊,可以為設(shè)計(jì)結(jié)構(gòu)較為簡單的程序?qū)崿F(xiàn)后臺(tái)任務(wù)的運(yùn)行刽虹,和Thread相比能夠更方便地和UI進(jìn)程進(jìn)行信息交互。然而它們都比較繁瑣呢诬。
在C# 4.0之后涌哲,Microsoft推出了Task。
O'REILLY出版的《C# 5.0 IN A NUTSHELL》 中指出Task彌補(bǔ)了Thread的不足:
A thread is a low-level tool for creating concurrency, and as such it has limitations. In particular:
- While it's easy to pass data into a thread that you start, there's no easy way to get a "return value" back from a thread that you Join. You have to set up some kind of shared field. And if the operation throws an exception, catching and propagating that exception is equally painful
- You can't tell a thread to start something else when it's finished; instead you must Join it (blocking your own thread in the process)
C#5.0之后推出了async和await關(guān)鍵詞:
These keywords let you write asynchronous code that has the same structure and simplicity as synchronous code, as well as eliminating the "plumbing" of asynchronous programming
Task與async/await關(guān)鍵詞兩者的結(jié)合使用尚镰,讓Asynchronous Programming能夠在Synchronous代碼的基礎(chǔ)快速改寫完成阀圾,換言之,就是簡單易用狗唉。
那么剩下最后一個(gè)核心問題初烘,如何取消與數(shù)據(jù)庫連接下載數(shù)據(jù)的這個(gè)進(jìn)程?
方案一:像關(guān)閉一個(gè)asynchronous method一樣分俯,將控制數(shù)據(jù)下載的進(jìn)程關(guān)閉肾筐。
根據(jù)這個(gè)思路,在《Entity Framework Core Cookbook》中指出:
All asynchronous methods take an optional CancellationToken parameter. This parameter, when supplied, provides a way for the caller method to cancel the asynchronous execution.
var source = new CancelationTokenSource();
var cancel = source.Token;
cancel.Register(()=>{
//cancelled
});
ctx.MyEntities.TolistAsync(cancel);
if(!cancel.WaitHandle.WaitOne(TimeSpan.FromSeconds(5))){
source.Cancel();
}
案例中的TolistAsync()方法稱為API Async Methods缸剪,標(biāo)志是以“Async”后綴結(jié)尾吗铐,它們的返回類型是Task(參見API Async Methods)
我的項(xiàng)目使用的是Entity Framework,于是引入CancellationToken杏节,調(diào)用.ToListAsync()唬渗,將SqlQuery改寫如下:
IList<print_pack_list_ext>query =
SysVariable.Database.SqlQuery<print_pack_list_ext>(sql.ToString(), parameters).ToListAsync(token);
應(yīng)用這個(gè)方法后,并沒有成功終止奋渔。網(wǎng)站上也有人遇到的了同樣的問題镊逝,聽說微軟團(tuán)隊(duì)針對Entity Framework尚未解決這個(gè)問題。無奈之下卒稳,當(dāng)時(shí)也有幾分自豪蹋半,居然被我找到了Bug~~
方案二:是否存在一個(gè)終止的方法,直接作用在SqlQuery上面呢充坑?
有减江,他就是:SqlCommand.Cancel Method ()染突,正如他的使命:
Tries to cancel the execution of a SqlCommand.
啊,終于找到了辈灼,他就是我的韓信份企。
那什么時(shí)候調(diào)用這名大將呢?應(yīng)該在用戶取消下載任務(wù)巡莹,使CancellationToken值為False之后被Invoke司志。正如CancellationToken.Register Method (Action)的使命:
Registers a delegate that will be called when this CancellationToken is canceled.
所以,將SqlCommand.Cancel注冊在CancelToken中即可:
using (CancellationTokenRegistration ctr = token.Register(() => cmd.Cancel()))
{
...
}
至此降宅,所有的疑惑都找到了答案骂远。
從底層向上走,首先DAO改寫如下:
public async Task<IList<print_pack_list_ext>> GetPackByDate(DateTime datefrom, DateTime dateto, CancellationToken token)
{
IList<print_pack_list_ext> list = new List<print_pack_list_ext>();
try
{
await Task<IList<print_pack_list_ext>>.Run(() =>
{
using (SqlConnection conn = new SqlConnection(getConnectionstring()))
{
conn.Open();
var cmd = conn.CreateCommand();
using (CancellationTokenRegistration ctr = token.Register(() => cmd.Cancel()))
{
#region sql string
string sqlString = "select a.pps_number,a.created_by from pack_list a where convert(datetime, a.created_datetime, 120) > convert(datetime, @dateFrom0, 120) and convert(datetime, a.created_datetime, 120) < convert(datetime, @dateFrom1, 120)"
#endregion
cmd.Parameters.AddWithValue("dateFrom0", datefrom);
cmd.Parameters.AddWithValue("dateFrom1", dateto);
cmd.CommandTimeout = 0;
cmd.CommandType = CommandType.Text;
cmd.CommandText = sqlString;
DataSet ds = new DataSet();
DataTable table = new DataTable();
table.Load(cmd.ExecuteReader());
ds.Tables.Add(table);
#region fill model
list = ds.Tables[0]
.AsEnumerable()
.Select(dataRow =>
new print_pack_list_ext
{
pps_number = dataRow.Field<string>("pps_number"),
created_by = dataRow.Field<string>("created_by")
}).ToList();
#endregion
}
}
}, token);
return list;
}
catch (SqlException ex)
{
return list;
}
}
在Controller中調(diào)用:
public async Task<IList<print_pack_list_ext>> GetPackByDate(DateTime datefrom, DateTime dateto, CancellationToken token)
{
return await _printPackListDAO.GetPackByDate(datefrom, dateto, token);
}
在View中實(shí)現(xiàn):
using System.Threading.Tasks;
using Solution.BusinessLayer;
using Solution.ExtendedEntity;
namespace Solution.Forms
{
public partial class FormLabelExportLog : Form
{
#region property
CancellationTokenSource tokenSource;
CancellationToken token;
LogController _logController = new LogController();
#endregion
#region event_button
//Code behind 'Generate Button'
private async void myBtnGenReport_Click(object sender, EventArgs e)
{
setProgressBarStyle_start();
myLabelInfo.Text = "Loading...";
string selectedItem = this.myListBox1.SelectedItem.ToString();
tokenSource = new CancellationTokenSource();
token = tokenSource.Token;
DataTable taskGetData = await Task.Factory.StartNew(() => loadData(selectedItem, token), token);
if (token.IsCancellationRequested)
MessageBox.Show("Cancelled Successfully!");
else
processData(taskGetData);
tokenSource.Dispose();
this.myLabelInfo.Text = "";
}
//Code behind 'Cancel' Button
private void myBtnCancelReport_Click(object sender, EventArgs e)
{
tokenSource.Cancel();
setProgressBarStyle_end();
}
#endregion
#region method_data
private DataTable loadData(string selectedItem, CancellationToken token)
{
#region initialization
DataTable dt = new DataTable();
DataRow dr;
...
#endregion
#region _PackListLog
if (selectedItem == _PackListLog)
{
IList<print_pack_list_ext> real = new List<print_pack_list_ext>();
real = _logController.GetPackByDate(datefrom, dateto, token).Result;
if (!token.IsCancellationRequested)
{
...
}
}
#endregion
return dt
}
private void processData(DataTable dt)
{
if (dt != null)
{
ExportToExcel(dt);
}
}
#endregion
}
}
就這樣愉快地完成了腰根。雖然是個(gè)小功能激才,牽扯到的很多知識(shí)點(diǎn)都沒有學(xué)過,翻閱了很多資料额嘿,雖然耗時(shí)較長瘸恼,但也是實(shí)習(xí)期間收獲最多,最有意義的時(shí)間册养。
P.S.
所有O'RELLY的書在SafariOnline都有东帅,郵箱注冊無需信用卡綁定免費(fèi)使用10天!球拦!真是良心~~
P.P.S
整理一下上面沒有提到的學(xué)習(xí)資料:
- 關(guān)于Task:
https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming - 當(dāng)不明白Task中括號(hào)里面的表達(dá)式靠闭,參見Lambda以及C# Delegate
- 什么是Async和Await,它們的實(shí)現(xiàn)原理是什么坎炼?
https://msdn.microsoft.com/library/hh191443(vs.110).aspx
https://msdn.microsoft.com/en-us/magazine/jj991977.aspx - 如何實(shí)現(xiàn)UI線程和非UI線程之間的交互阎毅?
https://msdn.microsoft.com/en-us/library/system.invalidoperationexception(v=vs.110).aspx - 什么是Callback
https://en.wikipedia.org/wiki/Callback_(computer_programming)
就用StackOverflow上的關(guān)于delegate,event点弯,callback的經(jīng)典回答結(jié)束這一篇啦,下期再見~~
I just met you
And this is crazy
But here's my number (delegate)
So if something happens (event)
Call me (callback)
Idea Matters