C#多線程應(yīng)用

在上一個(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)程弃榨。

這里將老師的課件對比案例引用如下:


Using Async Task in Android

與一般的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í)候,這兩篇教程相見恨晚:

  1. https://www.dotnetperls.com/backgroundworker-introduction
  2. http://omegacoder.com/?p=642

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:

  1. 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
  2. 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í)資料:

就用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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末矿咕,一起剝皮案震驚了整個(gè)濱河市抢肛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌碳柱,老刑警劉巖捡絮,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異莲镣,居然都是意外死亡福稳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進(jìn)店門瑞侮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來的圆,“玉大人鼓拧,你說我怎么就攤上這事≡铰瑁” “怎么了季俩?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長梅掠。 經(jīng)常有香客問我酌住,道長,這世上最難降的妖魔是什么阎抒? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任酪我,我火速辦了婚禮,結(jié)果婚禮上且叁,老公的妹妹穿的比我還像新娘都哭。我一直安慰自己,他們只是感情好谴古,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布质涛。 她就那樣靜靜地躺著,像睡著了一般掰担。 火紅的嫁衣襯著肌膚如雪汇陆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天带饱,我揣著相機(jī)與錄音毡代,去河邊找鬼。 笑死勺疼,一個(gè)胖子當(dāng)著我的面吹牛教寂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播执庐,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼酪耕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了轨淌?” 一聲冷哼從身側(cè)響起迂烁,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎递鹉,沒想到半個(gè)月后盟步,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡躏结,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年却盘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,745評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡黄橘,死狀恐怖兆览,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情旬陡,我是刑警寧澤拓颓,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站描孟,受9級特大地震影響驶睦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匿醒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一场航、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧廉羔,春花似錦溉痢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至竹挡,卻和暖如春镀娶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背揪罕。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工梯码, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人好啰。 一個(gè)月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓轩娶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親框往。 傳聞我的和親對象是個(gè)殘疾皇子鳄抒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 贈(zèng)言 03年8月 金榜題名沐春風(fēng) 最是人生得意時(shí) 欲知前途風(fēng)光好 登上廣寒折桂枝
    天行健君馬甲閱讀 157評論 0 4
  • 終于 陽光灑下 撫摸著我 如絲綢般的觸感 我微笑著 這蒼白的臉龐 在我們的 那個(gè)寒冷的世界 —— 世界里 你踏著飛...
    三月煙霞閱讀 218評論 0 0
  • 【天天棒棒】20171117學(xué)習(xí)力七期踐行D31 閱讀《小豬唏哩呼嚕》20分鐘椰弊。今天又換了一本是唏哩呼嚕和豬八戒嘁酿。...
    gxl水月亮閱讀 100評論 0 0
  • 獨(dú)自一個(gè)人吃飯就喜歡琢磨別人,尤其是在這種不太好找的小酒館里男应。 我來的時(shí)候1層有個(gè)攝影師在拍攝食物,那些刺身拼盤被...
    辛默默閱讀 406評論 2 3