環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※回頭車貨運收費標準
※推薦評價好的iphone維修中心
※超省錢租車方案
※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!
※推薦台中搬家公司優質服務,可到府估價
摘錄自2020年06月21日中央通訊社印尼報導
受武漢肺炎疫情影響,可健身又可避開大眾運輸人潮的自行車成為通勤最佳選擇。雅加達省政府指出,疫情後自行車族增加,兩大主要幹道每天固定時段都會隔出臨時自行車道。
印尼第一大報羅盤報(Kompas)19日報導,雅加達使用中的自行車道只有25公里,仍在計劃、施工、驗收中的自行車道有63公里。雅加達自6月初逐漸鬆綁為控制2019冠狀病毒疾病(COVID-19,武漢肺炎)疫情實施的管制措施,民眾陸續恢復上班。
雅加達省政府交通局長夏孚林(Syafrin Liputo)指出,雅加達省政府近日已開始與警方合作,每天固定時段會在雅加達中區的蘇迪爾曼大道(Sudirman)和塔林大道(Thamrin)兩條主要幹道,使用路錐,隔出臨時自行車道,方便自行車族使用,全長約14公里。
自行車業者表示,疫情初期業績暴跌,最近業績突然成長,賣最好的是折疊式腳踏車,因為民眾勤於運動,需求大增,他要向供應商調貨也遇到供貨吃緊的問題。
生活環境
能源轉型
國際新聞
印尼
雅加達
自行車
武漢肺炎
疫情下的食衣住行
交通運輸
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※回頭車貨運收費標準
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※推薦評價好的iphone維修中心
※教你寫出一流的銷售文案?
※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!
※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!
車身尺寸為4675*1780*1500mm,軸距達到了2710mm,乘坐空間上是比較寬敞的,加上柔軟的座椅,舒適性表現出色。動力方面,提供了1。2T和1。6T兩款發動機可選,匹配5擋手動或者6擋手自一體變速器,沉穩線性的油門調校讓動力非常容易掌控,動力總體表現出色。
隨着上個月的“超級丹”出軌事件后
辛辛苦苦塑造的男神形象毀於一旦
當然,更重要的是有媳婦的人被看得更緊了
上街口買串魚蛋都要向老婆申請
當然這隻是調侃一下
畢竟出軌並不是什麼好事情
但是隨着科技的發展
一種叫“車道偏離預警系統”的東西出現了
裝配在汽車上能有效提升行駛安全性
其工作原理是這樣的:在駕駛員未打轉向燈的情況下,電腦檢測到車輛偏離原車道之後,發出警報,以方向盤震動、發出警示音提醒等多種途徑提醒駕駛員,讓駕駛員及時糾正放向,有效減小事故的發生,哪些車上都有這種配置了,以下幾款15萬左右的合資車就有了。
北京現代-領動
指導價:9.98-15.18萬
一說起韓國品牌,就不得不提那大膽設計的外觀了,領動的設計是比較激進的,視覺衝擊力極強,狹長的大燈造型和碩大的六邊形中網給人留下很深刻的印象;純黑的內飾搭配紅色縫線整體感覺非常運動,中控液晶屏幕支持Carplay和CarLife功能,配置上很豐富,旗艦型車型上甚至裝備了主動安全系統。
2700mm的軸距為其乘坐空間提供了保證,462L的後備箱容積和可4/6比例放倒的後排座椅都能滿足日常使用的需求;其中1.4T車型採用了缸內直噴技術,最大功率為130馬力,搭配7速雙離合變速箱,動力的輸出也相對線性,對於渦輪遲滯的控制很好,幾乎感覺不到什麼遲滯現象。
東風雪鐵龍-雪鐵龍C4L
指導價:13.19-18.99萬
新款C4L的前臉變化是很明顯的,採用了上下橫貫式的進氣格柵,從雙立人車標延伸出來的鍍鉻飾條融入大燈內,看起來相當科幻前衛;內飾的變化卻沒外觀來的那麼強烈,與現款相差無幾,一些細節和配置上的升級更人性化了,电子手剎和全新樣式的換擋桿,操作起來更加方便順手了。
車身尺寸為4675*1780*1500mm,軸距達到了2710mm,乘坐空間上是比較寬敞的,加上柔軟的座椅,舒適性表現出色;動力方面,提供了1.2T和1.6T兩款發動機可選,匹配5擋手動或者6擋手自一體變速器,沉穩線性的油門調校讓動力非常容易掌控,動力總體表現出色。
東風本田-思域
指導價:11.59-16.99萬
思域整車採用全新的家族式設計語言打造,相比老款車型更具肌肉感,更多曲麵線條的運用,讓它在外觀方面更加飽滿立體,運動氣息很濃厚;內飾也是一改以往本田的風格,階梯狀設計的中控台錯落有致,大量的軟質材料覆蓋件也提高了不少檔次感,中央液晶儀錶盤功能豐富,显示效果出色。
2700mm的軸距使得新思域的空間有所改善,乘坐空間令人滿意,儲物空間也是比較多,半封閉的中央扶手箱是其中的亮點;1.5T發動機最大功率177馬力,搭配CVT變速器,起步輕盈暢快,動力輸出一氣呵成,渦輪介入感極其輕微,給人加速很線性的體驗,可以說這代思域迎來了脫胎換骨的一代。
總結:三款車的類型各有特色,適合的人群也更廣,配置豐富外觀帥氣的領動,底盤沉穩操控感好的雪鐵龍C4L,動力強勁高顏值的思域,到底哪個才是你的菜!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能
※台北網頁設計公司這麼多該如何選擇?
※智慧手機時代的來臨,RWD網頁設計為架站首選
※評比南投搬家公司費用收費行情懶人包大公開
※回頭車貨運收費標準
※網頁設計最專業,超強功能平台可客製化
※別再煩惱如何寫文案,掌握八大原則!
5L地球夢科技發動機,可以爆發出131匹強勁馬力,無論是起步加速還是中後段加速,全新鋒范的動力響應都表現的非常迅速,百公里10。2秒的加速時間更是在同級中名列前茅,滿足了年輕人的駕駛慾望。這樣的動力總成和強勁的加速性能和無論是對男主角時光,還是對很多追求速度的年輕人來說,都是很有吸引力的。
你難以靠近難以不再想念
我難以抗拒你容顏
把心畫在寫給你的信中
希望偶爾能夠見到你微笑的容顏
……
當這首熟悉的《難以抗拒你容顏》在電視里響起時,應該有無數人跟着哼唱了吧。沒錯,小編說的就是最近火爆的電視劇《咱們相愛吧》里女主之一蔡春娜在KTV里唱這首歌以至潸然淚下的劇情。這首歌勾起了她與劇中前男友的回憶,又何嘗沒有勾起大家對青春的回憶呢,簡直經典啊!所以這部劇紅也不是沒有道理,不僅身邊上至80歲跳廣場舞的阿姨下至18歲青春活潑的姑娘都沉迷其中,各大視頻網站也都佔據熱搜。
只見地鐵哪怕擠成狗,都有不少人堅決舉起手機在看;公司中午休息,女同事沉迷於劇情飯都顧不上吃;就連晚上跳廣場舞的大媽都提前結束回家追劇了。在好奇心的驅使下,小編也跟着看了起來。
而八點檔電視劇總有一種迷の魅力,一旦靠近就不可自拔。結果你們也猜到了,基本上是這樣的↓↓↓
但是!小編當然不是一個沉迷追劇不務正業的人,生活在哪裡汽車就在哪裡,電視看着看着就自然對裏面出現的汽車更關心一點,今天就來給大家八一八這部劇里出現頻率頗高的轎車——全新鋒范。
在劇里,它是男主角時光以及大毛的座駕,而兩位車主一個是著名的建築設計師,一個則是深藏不露的富二代,都不是“一般人”。 生活中小編身邊不少全新鋒范車主,也都是優質青年,可見這輛車對高品位人群的吸引力。
男主角時光作為一位建築設計師,對外觀的要求絕不會低。全新鋒范的外觀設計,恰恰符合了他的要求。飛翼式前臉突出凌厲與飽滿,車身上下雙腰線簡潔幹練,輪廓鮮明。翹起的尾部採用寬大設計,搭配鑽石切割的后尾燈組合,顯得動感又不失時尚。這種簡約不簡單的風格,與很多現代建築也是交相呼應的。
內飾未來感十足,布局層次分明。簡潔大氣的儀錶盤與中央的中控大屏、觸控式智能空調營造出科技感十足的駕駛氛圍,恍惚間會讓你覺得是在開一輛“未來之車”。
當然,全新鋒范的動力應該也是男主角時光選擇它的重要原因。這樣一個在生活中方方面面都追求完美的霸道總裁對動力也不會妥協。好在全新鋒范搭載的是本田1.5L地球夢科技發動機,可以爆發出131匹強勁馬力,無論是起步加速還是中後段加速,全新鋒范的動力響應都表現的非常迅速,百公里10.2秒的加速時間更是在同級中名列前茅,滿足了年輕人的駕駛慾望。這樣的動力總成和強勁的加速性能和無論是對男主角時光,還是對很多追求速度的年輕人來說,都是很有吸引力的。
而劇中另外一位開全新鋒范的主角大毛,一開始只是一個默默喜歡女主角芝芝的上班族,沒想到最後才知道,他的父親其實是有名的商人。
但大毛對此顯得不以為意,依舊每天開着一輛紅色全新鋒范靠自己的努力打拚一番事業。其實除掉富二代這個身份后,大毛和現在眾多打拚的年輕人並沒有區別,相比華而不實的超跑,一輛十萬級別,經濟適用的轎車反而才是事業上最大的助力。
與全新鋒范1.5L地球夢科技發動機相匹配的是支持自動啟停功能的CVT無級變速箱,平順、有力的換擋過程為駕駛者帶來暢快的加速體驗以及低至5.4L的百公里油耗表現。如此經濟的表現對於年輕人來說更是大大減輕了用車壓力。並且全新鋒范搭載了時下先進的科技配置,中控7英寸多功能互聯显示系統使車型氛圍充滿了未來感,除了可與多種主流手機型號連接並同步操作以外,屏幕內還集成了先進的三模式倒車影像,為駕駛者提供了一個清晰、直觀的後方視角,使車輛在倒車時更有安全保障。全新鋒范強大的功能配置以及突出的性價比,堪稱生活好幫手、創業好夥伴,難怪能成為了大毛的選擇。
綜合來看,決意東山再起的時光與憑藉自身努力創業的大毛選擇全新鋒范作為座駕再適合不過,外觀內飾極具現代感,高科技配置豐富,低油耗高動力,一切都恰好滿足年輕人的需求。並且不久前的廣州車展上,廣汽本田又推出了2017款鋒范1.5L CVT精英版,新增多達7項配置,使得外觀造型與實用性上都得到了更好的提升。在外觀上加入了15寸鋁質輪轂與後置短天線。在實用性方面加入了對於如今年輕人來說不可或缺的無鑰匙進入/一鍵啟動功能,靠近車門自動解鎖,出發前的系列動作一氣呵成,說不出的瀟洒與帥氣。而後排出風口的出現,在讓所有坐上你車的人都能得到享受的同時,更是使得整輛車達到了越級的表現。
2017款鋒范1.5L CVT精英版的官方指導價為9.58萬元。如此豐富的配置,售價卻未超出10萬,再加上即將結束的購置稅減半政策,性價比在同級車型中非常突出,可見廣汽本田的十足誠意。所以十萬級合資轎車中,2017款鋒范不失為一個上上之選。
追劇的同時不忘給大家推薦車型,小編感覺自己今天一米八!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
※回頭車貨運收費標準
但是就是銷量一般,很大一部分原因是消費者對自主品牌的不信任,也有一些人覺得艾瑞澤7價格稍貴,畢竟它的親弟弟艾瑞澤5的銷量非常好。艾瑞澤7的售價區間為7。99-9。59萬元,1。5T車型的起售價也是為7。99萬元,預測同等配置的1。
最近,我們得到了一些關於奇瑞艾瑞澤7的1.5T自動擋車型的路試照片,新車將會在2017年上市銷售。現款的艾瑞澤7的1.5T車型只配備了5擋手動變速箱,這對於那些既想要大動力又想要自動擋的消費者就感到比較痛苦了。
選擇1.6L車型吧,動力較弱。選擇1.5T車型吧!動力是足夠了,但是就是手動擋開着會比較累。所以奇瑞也意識到了這個問題,抓緊給艾瑞澤7的1.5T車型配備自動擋,豐富自己的產品線,以滿足消費者日趨多樣化的選擇。
從外觀上來看,這次曝光的1.5T自動擋車型和現款車型基本一致,畢竟只是增加了一款車型,又不是改款或者換代,所以外觀是不會有很大變化的。
前臉依舊是“X”造型的設計,前大燈靠內側部分採用了熏黑的處理,以增加運動感。現款車型已經是標配了日間行車燈了,作為自動檔車型自然不可缺少。尾部的“TCI”說明了它是1.5T車型的身份。
內飾也是和現款基本一致,看起來也比較時尚,目測做工應該不錯。主要的變化是由手動擋換車了自動擋。同時配置也比較豐富,ESp、上坡輔助、胎壓監測、倒車雷達、真皮方向盤、GpS、中控大屏幕、藍牙、手機映射等都會配備。
至於動力系統,新車肯定搭載1.5L渦輪增壓發動機,但是變速箱現在還沒有確定的消息,有可能是CVT,也有可能是和瑞虎7一樣的6速雙離合。不過,編者推測雙離合的概率會更大一點。
其實編者一直覺得艾瑞澤7是一款質感非常不錯的車子,四輪獨立懸挂,1.5T發動機動力十足,配置也很厚道,做工也不錯,行駛質感在同級別處於領先地位,操控較好。但是就是銷量一般,很大一部分原因是消費者對自主品牌的不信任,也有一些人覺得艾瑞澤7價格稍貴,畢竟它的親弟弟艾瑞澤5的銷量非常好。
艾瑞澤7的售價區間為7.99-9.59萬元,1.5T車型的起售價也是為7.99萬元,預測同等配置的1.5T自動擋車型將會比手動擋車型最低貴8000元,所以自動擋的售價將會接近9萬元。對於這個價格,你能接受么?
競爭對手
吉利帝豪GL
指導價:7.88-11.38萬
帝豪GL的1.3T自動擋車型的起售價為9.78萬,這價格要比艾瑞澤7稍微貴一點,但是帝豪GL的熱度要比艾瑞澤7大很多。作為目前比較熱門的一款車,帝豪GL將是艾瑞澤7的強大對手。
長安逸動
指導價:8.09-24.99萬
逸動的自動擋車型的起步價為8.99萬元,搭配4AT變速箱,目前還有着七八千的優惠,雖然價格可能比艾瑞澤7便宜,但是逸動的發動機為1.6L 125馬力。
一汽-大眾-寶來
指導價:10.78-15.38萬
寶來自動擋的起售價為11.98萬,目前也有着一兩萬的優惠,是加一些錢買寶來還是直接買艾瑞澤7?這是一個值得思考的問題。
所以說艾瑞澤7上市之後會面臨很多強大的對手,但是編者相信只要價格足夠接地氣,就不用發愁銷量問題。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
※回頭車貨運收費標準
內飾Q50L的內飾風格顯得有些個性,並不如外觀給人感覺那麼的運動,更偏向一台家用車,但由於豪華品牌的定位,用料做工層面還是非常厚道細膩的類型。由於也是前置后驅,後排同樣存在這中間地台較高的特點。動力Q50L搭載的2。
說到30-40萬的豪華品牌車型,不少人第一主觀就會聯想到德系三強BBA,儘管BBA品牌雄踞豪華車市場銷量榜前列多年,但是路面曝光率過高的事實也不免顯得稍微喪失了一些個性,其實在豪華車市場,還有不少新晉的車型。
捷豹XFL
推薦車型:2.0T 200pS風華版
指導價格:38.80萬
捷豹XFL是捷豹品牌在中國首款國產的車型,對標車型是奔馳E級,寶馬5系這類豪華品牌中大型車,但是從定價來看,XFL的入門定價顯得頗為“謙虛”,38.80萬的入門車型配置已經非常不錯。
外觀
XFL的外觀設計呈現的車身姿態比較低矮,真的就宛如一隻卧身伏擊的豹子,囂張的前臉更凸顯了它品牌血統里就富有的攻擊氣質。
車尾設計優雅沉穩,並沒有太多視覺上覺得突兀的地方,或許這也是英國車的一種特點吧;XFL車身修長,超過5米,但並不顯得生拉硬拽,整車比例相當協調,線條勾勒手法流暢柔和,表明了它同樣是一台尺寸傲人的豪華轎車。
內飾
作為最低配的風華版,車內裝飾並沒有使用高配車型上所使用的撞色設計,但根據市場反饋,似乎消費者對於全黑內飾並不會抗拒。
XFL內飾秉承了捷豹家族式的風格,採用了整體環抱式的設計理念,讓前排人員駕乘時的包裹感更加強烈。
由於是前置后驅的驅動形式,後排地板因為傳動軸的原因導致地台較高,乘坐三人稍微有些不便利。
動力
200pS 風華版搭載了一台2.0T渦輪增壓發動機,最大馬力200匹,峰值扭矩320牛米,動力參數上領先於同級別主流的豪華品牌車型。與之匹配的是一台8擋手自一體變速箱,日常行駛動力儲備相當充沛,變速箱的平順性也非常出色。
英菲尼迪Q50L
推薦車型:2016款 2.0T悅享版
指導價格:30.98萬
英菲尼迪是誕生於北美地區的日產豪華品牌,旗下的Q50L是一款性價比較高的中型三廂車,對錶車型是奧迪A4L、寶馬3系這一級別的豪華品牌車型。作為豪華品牌車型,27.98萬的起售門檻還算不錯。
外觀
Q50L同樣也是作為東風英菲尼迪在華國產的首款車型,相較於進口Q50,Q50L的軸距加長了48mm,車身加長50mm,更大尺寸的車身和更長的軸距也讓Q50L更向“豪華”層面靠攏一分。
英菲尼迪Q50L採用了“長車頭短車尾”的造型設計,視覺效果保持一個上揚的態勢,營造出了一種較為兇狠的感覺,車尾緊湊飽滿,外露的雙邊排氣管凸顯了轎跑車型的運動范兒,Q50L偏運動風格的造型也是年輕人更喜歡的類型。
內飾
Q50L的內飾風格顯得有些個性,並不如外觀給人感覺那麼的運動,更偏向一台家用車,但由於豪華品牌的定位,用料做工層面還是非常厚道細膩的類型;由於也是前置后驅,後排同樣存在這中間地台較高的特點。
動力
Q50L搭載的2.0T發動機,最大馬力211匹,峰值扭矩350牛米,匹配的是7速手自一體變速箱,整體調校稍微有些激進,配合上略顯沉重但是十分精準的轉向,Q50L是一台極其富有駕駛樂趣的三廂車。
林肯MKZ
推薦車型:2017款 2.0T尊享版
指導價格:30.48萬
林肯和凱迪拉克一樣,都曾經作為美國總統用車的品牌,只是凱迪拉克在中國市場耕耘已久,而林肯則更多的是作為後起之秀意圖在國內立足。林肯MKZ是目前林肯旗下定位相對親民的一款三廂轎車,起步售價28.48萬,這個價位也引起了不少目標車型在ATS-L、寶馬3系這類車型潛在買家的關注。
外觀:
2017款的MKZ外觀借鑒了林肯自家旗艦車型Continental(大陸)的設計,未來這種尺寸更大,風格更沉穩的進氣格柵設計很有可能成為林肯最新的家族風格。MKZ整體外觀並不激進,圓潤大氣的設計理念讓它彰顯出一種行政級的嚴肅氣質。車尾標誌性的貫穿式尾燈永遠是林肯品牌的辨別武器,就目前來說,一台林肯MKZ還是非常小眾的存在。
內飾:
作為福特旗下的豪華品牌,MKZ的內飾風格看上去高檔感還是相對出色,中間按鍵式的換擋機構對於眾多消費者來說一時間可能難以適應,不過操作便利程度還是很高。由於MKZ是前驅車型,後排地台相對平整,在乘坐空間上的的捨棄程度沒前兩款那麼高,後排空間相對充裕。
動力:
林肯MKZ搭載的是2.0T渦輪增壓發動機,最大馬力203匹,峰值扭矩381牛米,匹配的是6速手自一體變速箱,調校風格也是偏向“福特”的類型,油門輕快,動力輸出很直接。
全文總結:現如今很多80後人群買車的預算區間已經上升到30萬的層級,但是多數人的選擇依舊停留在BBA,不是說BBA不好,而是在崇尚個性化的當下,選擇一款不一樣的,也正是符合年輕人個性化訴求的選擇。
以上三款車型,捷豹接近40萬的售價或許對於大多數人來說還是高了,所以推薦在Q50L和MKZ之間做抉擇;而喜歡駕駛樂趣和操控感的可以考慮Q50L,后驅布局,動力不俗;
偏向沉穩舒適訴求的朋友則可以考慮林肯MKZ,畢竟定位就是一款偏向舒適的中型車。亦個性亦豪華,三十萬級別這兩款都是不錯的選擇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※別再煩惱如何寫文案,掌握八大原則!
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※回頭車貨運收費標準
※台中搬家公司費用怎麼算?
本文是我在閱讀 Koa 源碼后,並實現迷你版 Koa 的過程。如果你使用過 Koa 但不知道內部的原理,我想這篇文章應該能夠幫助到你,實現一個迷你版的 Koa 不會很難。
本文會循序漸進的解析內部原理,包括:
application.js: 入口文件,裡面包括我們常用的 use 方法、listen 方法以及對 ctx.body 做輸出處理context.js: 主要是做屬性和方法的代理,讓用戶能夠更簡便的訪問到request和response的屬性和方法request.js: 對原生的 req 屬性做處理,擴展更多可用的屬性和方法,比如:query 屬性、get 方法response.js: 對原生的 res 屬性做處理,擴展更多可用的屬性和方法,比如:status 屬性、set 方法用法:
const Coa = require('./coa/application')
const app = new Coa()
// 應用中間件
app.use((ctx) => {
ctx.body = '<h1>Hello</h1>'
})
app.listen(3000, '127.0.0.1')
application.js:
const http = require('http')
module.exports = class Coa {
use(fn) {
this.fn = fn
}
// listen 只是語法糖 本身還是使用 http.createServer
listen(...args) {
const server = http.createServer(this.callback())
server.listen(...args)
}
callback() {
const handleRequest = (req, res) => {
// 創建上下文
const ctx = this.createContext(req, res)
// 調用中間件
this.fn(ctx)
// 輸出內容
res.end(ctx.body)
}
return handleRequest
}
createContext(req, res) {
let ctx = {}
ctx.req = req
ctx.res = res
return ctx
}
}
基礎版本的實現很簡單,調用 use 將函數存儲起來,在啟動服務器時再執行這個函數,並輸出 ctx.body 的內容。
但是這樣是沒有靈魂的。接下來,實現 context 和中間件原理,Koa 才算完整。
ctx 為我們擴展了很多好用的屬性和方法,比如 ctx.query、ctx.set()。但它們並不是 context 封裝的,而是在訪問 ctx 上的屬性時,它內部通過屬性劫持將 request 和 response 內封裝的屬性返回。就像你訪問 ctx.query,實際上訪問的是 ctx.request.query。
說到劫持你可能會想到 Object.defineProperty,在 Kao 內部使用的是 ES6 提供的對象的 setter 和 getter,效果也是一樣的。所以要實現 ctx,我們首先要實現 request 和 response。
在此之前,需要修改下 createContext 方法:
// 這三個都是對象
const context = require('./context')
const request = require('./request')
const response = require('./response')
module.exports = class Coa {
constructor() {
this.context = context
this.request = request
this.response = response
}
createContext(req, res) {
const ctx = Object.create(this.context)
// 將擴展的 request、response 掛載到 ctx 上
// 使用 Object.create 創建以傳入參數為原型的對象,避免添加屬性時因為衝突影響到原對象
const request = ctx.request = Object.create(this.request)
const response = ctx.response = Object.create(this.response)
ctx.app = request.app = response.app = this;
// 掛載原生屬性
ctx.req = request.req = response.req = req
ctx.res = request.res = response.res = res
request.ctx = response.ctx = ctx;
request.response = response;
response.request = request;
return ctx
}
}
上面一堆花里胡哨的賦值,是為了能通過多種途徑獲取屬性。比如獲取 query 屬性,可以有 ctx.query、ctx.request.query、ctx.app.query 等等的方式。
如果你覺得看起來有點冗餘,也可以主要理解這幾行,因為我們實現源碼時也就用到下面這些:
const request = ctx.request = Object.create(this.request)
const response = ctx.response = Object.create(this.response)
ctx.req = request.req = response.req = req
ctx.res = request.res = response.res = res
request.js:
const url = require('url')
module.exports = {
/* 查看這兩步操作
* const request = ctx.request = Object.create(this.request)
* ctx.req = request.req = response.req = req
*
* 此時的 this 是指向 ctx,所以這裏的 this.req 訪問的是原生屬性 req
* 同樣,也可以通過 this.request.req 來訪問
*/
// 請求的 query 參數
get query() {
return url.parse(this.req.url).query
},
// 請求的路徑
get path() {
return url.parse(this.req.url).pathname
},
// 請求的方法
get method() {
return this.req.method.toLowerCase()
}
}
response.js:
module.exports = {
// 這裏的 this.res 也和上面同理
// 返回的狀態碼
get status() {
return this.res.statusCode
},
set status(val) {
return this.res.statusCode = val
},
// 返回的輸出內容
get body() {
return this._body
},
set body(val) {
return this._body = val
},
// 設置頭部
set(filed, val) {
if (typeof filed === 'string') {
this.res.setHeader(filed, val)
}
if (toString.call(filed) === '[object Object]') {
for (const key in filed) {
this.set(key, filed[key])
}
}
}
}
通過上面的實現,我們可以使用 ctx.request.query 來訪問到擴展的屬性。但是在實際應用中,更常用的是 ctx.query。不過 query 是在 request 的屬性,通過 ctx.query 是無法訪問的。
這時只需稍微做個代理,在訪問 ctx.query 時,將 ctx.request.query 返回就可以實現上面的效果。
context.js:
module.exports = {
get query() {
return this.request.query
}
}
實際的代碼中會有很多擴展的屬性,總不可能一個一個去寫吧。為了優雅的代理屬性,Koa 使用 delegates 包實現。這裏我就直接簡單封裝下代理函數,代理函數主要用到__defineGetter__ 和 __defineSetter__ 兩個方法。
在對象上都會帶有 __defineGetter__ 和 __defineSetter__,它們可以將一個函數綁定在當前對象的指定屬性上,當屬性被獲取或賦值時,綁定的函數就會被調用。就像這樣:
let obj = {}
let obj1 = {
name: 'JoJo'
}
obj.__defineGetter__('name', function(){
return obj1.name
})
此時訪問 obj.name,獲取到的是 obj1.name 的值。
了解這個兩個方法的用處后,接下來開始修改 context.js:
const proto = module.exports = {
}
// getter代理
function delegateGetter(prop, name){
proto.__defineGetter__(name, function(){
return this[prop][name]
})
}
// setter代理
function delegateSetter(prop, name){
proto.__defineSetter__(name, function(val){
return this[prop][name] = val
})
}
// 方法代理
function delegateMethod(prop, name){
proto[name] = function() {
return this[prop][name].apply(this[prop], arguments)
}
}
delegateGetter('request', 'query')
delegateGetter('request', 'path')
delegateGetter('request', 'method')
delegateGetter('response', 'status')
delegateSetter('response', 'status')
delegateGetter('response', 'body')
delegateSetter('response', 'body')
delegateMethod('response', 'set')
中間件思想是 Koa 最精髓的地方,為擴展功能提供很大的幫助。這也是它雖然小,卻很強大的原因。還有一個優點,中間件使功能模塊的職責更加分明,一個功能就是一个中間件,多个中間件組合起來成為一個完整的應用。
下面是著名的“洋蔥模型”。這幅圖很形象的表達了中間件思想的作用,它就像一個流水線一樣,上游加工后的東西傳遞給下游,下游可以繼續接着加工,最終輸出加工結果。
在調用 use 註冊中間件的時候,內部會將每个中間件存儲到數組中,執行中間件時,為其提供 next 參數。調用 next 即執行下一个中間件,以此類推。當數組中的中間件執行完畢后,再原路返回。就像這樣:
app.use((ctx, next) => {
console.log('1 start')
next()
console.log('1 end')
})
app.use((ctx, next) => {
console.log('2 start')
next()
console.log('2 end')
})
app.use((ctx, next) => {
console.log('3 start')
next()
console.log('3 end')
})
輸出結果如下:
1 start
2 start
3 start
3 end
2 end
1 end
有點數據結構知識的同學,很快就想到這是一個“棧”結構,執行的順序符合“先入后出”。
下面我將內部中間件實現原理進行簡化,模擬中間件執行:
function next1() {
console.log('1 start')
next2()
console.log('1 end')
}
function next2() {
console.log('2 start')
next3()
console.log('2 end')
}
function next3() {
console.log('3 start')
console.log('3 end')
}
next1()
執行過程:
next1,將其入棧執行,輸出 1 startnext2 函數,將其入棧執行,輸出 2 startnext3 函數,將其入棧執行,輸出 3 start3 end,函數執行完畢,next3 彈出棧2 end,函數執行完畢,next2 彈出棧1 end,函數執行完畢,next1 彈出棧相信通過這個簡單的例子,都大概明白中間件的執行過程了吧。
中間件原理實現的關鍵點主要是 ctx 和 next 的傳遞。
function compose(middleware) {
return function(ctx) {
return dispatch(0)
function dispatch(i){
// 取出中間件
let fn = middleware[i]
if (!fn) {
return
}
// dispatch.bind(null, i + 1) 為應用中間件接受到的 next
// next 即下一個應用中間件
fn(ctx, dispatch.bind(null, i + 1))
}
}
}
可以看到,實現過程本質是函數的遞歸調用。在內部實現時,其實 next 沒有做什麼神奇的操作,它就是下一个中間件調用的函數,作為參數傳入供使用者調用。
下面我們來單獨測試 compose,你可以將它粘貼到控制台上運行:
function next1(ctx, next) {
console.log('1 start')
next()
console.log('1 end')
}
function next2(ctx, next) {
console.log('2 start')
next()
console.log('2 end')
}
function next3(ctx, next) {
console.log('3 start')
next()
console.log('3 end')
}
let ctx = {}
let fn = compose([next1, next2, next3])
fn(ctx)
最後,因為 Koa 中間件是可以使用 async/await 異步執行的,所以還需要修改下 compose 返回 Promise:
function compose(middleware) {
return function(ctx) {
return dispatch(0)
function dispatch(i){
// 取出中間件
let fn = middleware[i]
if (!fn) {
return Promise.resolve()
}
// dispatch.bind(null, i + 1) 為應用中間件接受到的 next
// next 即下一個應用中間件
try {
return Promise.resolve( fn(ctx, dispatch.bind(null, i + 1)) )
} catch (error) {
return Promise.reject(error)
}
}
}
}
實現完成中間件的邏輯后,將它應用到迷你版Koa中,原來的代碼邏輯要做一些修改(部分代碼忽略)
application.js:
module.exports = class Coa {
constructor() {
// 存儲中間件的數組
this.middleware = []
}
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
// 將中間件加入數組
this.middleware.push(fn)
return this
}
listen(...args) {
const server = http.createServer(this.callback())
server.listen(...args)
}
callback() {
const handleRequest = (req, res) => {
// 創建上下文
const ctx = this.createContext(req, res)
// fn 為第一個應用中間件
const fn = this.compose(this.middleware)
// 在所有中間件執行完畢后 respond 函數用於處理 ctx.body 輸出
return fn(ctx).then(() => respond(ctx)).catch(console.error)
}
return handleRequest
}
compose(middleware) {
return function(ctx) {
return dispatch(0)
function dispatch(i){
let fn = middleware[i]
if (!fn) {
return Promise.resolve()
}
// dispatch.bind(null, i + 1) 為應用中間件接受到的 next
try {
return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)))
} catch (error) {
return Promise.reject(error)
}
}
}
}
}
function respond(ctx) {
let res = ctx.res
let body = ctx.body
if (typeof body === 'string') {
return res.end(body)
}
if (typeof body === 'object') {
return res.end(JSON.stringify(body))
}
}
application.js:
const http = require('http')
const context = require('./context')
const request = require('./request')
const response = require('./response')
module.exports = class Coa {
constructor() {
this.middleware = []
this.context = context
this.request = request
this.response = response
}
use(fn) {
if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
this.middleware.push(fn)
return this
}
listen(...args) {
const server = http.createServer(this.callback())
server.listen(...args)
}
callback() {
const handleRequest = (req, res) => {
// 創建上下文
const ctx = this.createContext(req, res)
// fn 為第一個應用中間件
const fn = this.compose(this.middleware)
return fn(ctx).then(() => respond(ctx)).catch(console.error)
}
return handleRequest
}
// 創建上下文
createContext(req, res) {
const ctx = Object.create(this.context)
// 處理過的屬性
const request = ctx.request = Object.create(this.request)
const response = ctx.response = Object.create(this.response)
// 原生屬性
ctx.app = request.app = response.app = this;
ctx.req = request.req = response.req = req
ctx.res = request.res = response.res = res
request.ctx = response.ctx = ctx;
request.response = response;
response.request = request;
return ctx
}
// 中間件處理邏輯實現
compose(middleware) {
return function(ctx) {
return dispatch(0)
function dispatch(i){
let fn = middleware[i]
if (!fn) {
return Promise.resolve()
}
// dispatch.bind(null, i + 1) 為應用中間件接受到的 next
// next 即下一個應用中間件
try {
return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)))
} catch (error) {
return Promise.reject(error)
}
}
}
}
}
// 處理 body 不同類型輸出
function respond(ctx) {
let res = ctx.res
let body = ctx.body
if (typeof body === 'string') {
return res.end(body)
}
if (typeof body === 'object') {
return res.end(JSON.stringify(body))
}
}
本文的簡單實現了 Koa 主要的功能。有興趣最好還是自己去看源碼,實現自己的迷你版 Koa。其實 Koa 的源碼不算多,總共4個文件,全部代碼包括註釋也就 1800 行左右。而且邏輯不會很難,很推薦閱讀,尤其適合源碼入門級別的同學觀看。
最後附上完整實現的代碼:github
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※教你寫出一流的銷售文案?
※超省錢租車方案
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※回頭車貨運收費標準
1.導讀
駕車導航是数字地圖的核心用戶場景,用戶在進行導航規劃時,高德地圖會提供給用戶3條路線選擇,由用戶根據自身情況來決定按照哪條路線行駛。
同時各路線的ETA(estimated time of arrival,預估到達時間)會直接显示給用戶,這是用戶關心的核心點之一。用戶給定起點和終點后,我們的任務是預測起終點的ETA,ETA的準確率越高,給用戶帶來的出行體驗越好。
2.基於深度學習模型的探索和實踐
2.1模型選擇
傳統機器學習模型在ETA中,比較常用的有線性回歸、RF(隨機森林)、GBDT(梯度提升決策樹)等回歸預測類模型。線性模型表達能力較差,需要大量特徵工程預先分析出有效的特徵;RF通過樣本隨機和特徵隨機的方式引入更多的隨機性,解決了決策樹泛化能力弱的問題;GBDT是通過採用加法模型(即基函數的線性組合),以及不斷減小訓練過程產生的殘差來達到回歸的算法。
傳統機器學習模型相對簡單易懂,也能達到不錯的效果,但存在兩個問題:
第一個問題很好理解,深度學習模型能很好地彌補這方面。針對第二個問題,以歷史速度信息選取存在的不確定性為例來說明一下,歷史速度信息是一個區分周一到周日七個工作日、10分鐘間隔的歷史平均時間,可以根據該路段的預計進入時間所在10分鐘區間來選定。如下圖(歷史平均速度)從0:00-24:00的變化曲線,可以看到一天中特別是早晚高峰,速度值存在較大波動。
而在選取歷史平均時間時,依賴的是預計進入時間,這個時間依賴於上游路段的預計通行時間,因此其選取存在不確定性,進而導致ETA計算不準確。
考慮到以上問題的存在,我們選擇利用RNN的時間序列思想將路線中上下游路段串聯起來進行路段ETA的預測。
另外考慮到RNN存在的長依賴問題,且結合實際業務情況,我們選擇使用LSTM模型來進行建模,LSTM的門結構具有的選擇性還能讓模型自行學習選擇保留哪些上游的特徵信息進行預測。
2.2網絡架構
上圖為整個模型的框架圖,主要分為兩部分,使用LSTM模塊對路線中的路段ETA的預測和最終使用N層全連接模塊對累計路段ETA及路線各特徵進行完整路線的ETA預測。
2.3路段ETA預測
上圖為各路段ETA預測使用的LSTM結構圖,Xt為路線中第t個路段的特徵信息,主要包含對應的實時路況信息、歷史路況信息、路段的靜態特徵等。
LSTM本是輸入時間序列數據的模型,我們利用該思想,將路線中各路段序列依次輸入模型。
2.4完整路線ETA預測
在LSTM模塊得到累計路線ETA預測值后,結合該路線的靜態屬性,使用全連接模塊將其整合成最終輸出的完整路線ETA預測值。
路線的屬性特徵主要指一些人工提取的特徵,如該路線的長度、導航規劃發起特徵日、是否早晚高峰時段等,用以加強模型在不同場景下的表達能力。
損失函數選用線性回歸常用的平方形式:MSE,公式如下:
其中,N是路線數量,ETA路線j為路線ETA,即預測值;用戶實走j為用戶在該路線的實走時間,即真值。
3.模型效果
衡量模型效果,即路線上ETA的預測值時,主要考慮的是準確率。一般情況下,用戶對ETA偏長和偏短的容忍度不同,對偏長容忍度更高。比如用戶要去機場,ETA給的時間偏短10分鐘比偏長10分鐘對用戶的損害更大。因此準確度的指標設計傾向於ETA偏長,定義為滿足用戶一定容忍範圍的請求比例,即準確率作為主要衡量指標。
在北京市上的實驗結果显示,ETA準確率得到提升,MSE loss下降比例28.2%,效果有了明顯的提升。
4.小結
本文介紹了引入深度學習模型,幫助建模導航規劃的預估到達時間預測,成功解決了線性模型的不足,也為後續引入更多特徵、進行更多探索打開了空間,如歷史速度信息的不確定度、時效性、周期性、突發事件、路網結構等。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※教你寫出一流的銷售文案?
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
※超省錢租車方案
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※推薦台中搬家公司優質服務,可到府估價
我們開發的系統,一般可以不用考慮語言國際化的問題,大多數系統一般是給本國人使用的,而且直接使用中文開發界面會更加迅速 一些,不過框架最好能夠支持國際化的處理,以便在需要的時候,可以花點時間來實現多語言切換的處理,使系統具有更廣泛的受眾用戶。VUE+Element 前端應用實現國際化的處理還是非常方便的,一般在Main.js函數裏面引入語言文件,然後在界面上進行一定的處理,把對應的鍵值轉換為對應語言的語義即可。本篇隨筆介紹在VUE+Element 前端應用中如何實現在界面快速的支持多語言國際化的處理邏輯代碼。
Element 組件內部默認使用中文,若希望使用其他語言,則需要進行多語言設置。以英文為例,在 main.js 中:
// 完整引入 Element import Vue from 'vue' import ElementUI from 'element-ui' import locale from 'element-ui/lib/locale/lang/en' Vue.use(ElementUI, { locale })
由於我們現在是需要處理多語言的切換,那麼,我們在src下面的一個目錄裏面創建一個lang目錄,在其中裏面編輯zh.js和en.js分別代表中英文語言對照信息,index.js文件則為引入這兩個文件的處理關係。
在index.js裏面,需要設置一個函數,用來獲取Cookie裏面存儲的語言,如果沒有找到,以瀏覽器國際化語言為準,如下代碼所示。
export function getLanguage() { const chooseLanguage = Cookies.get('language') if (chooseLanguage) return chooseLanguage // 如果沒有選擇語言,那麼使用瀏覽器語言 const language = (navigator.language || navigator.browserLanguage).toLowerCase() const locales = Object.keys(messages) for (const locale of locales) { if (language.indexOf(locale) > -1) { return locale } } return 'en' }
其中代碼行
const locales = Object.keys(messages)
是獲取message對象裏面的鍵,如下所示。
import Vue from 'vue' import VueI18n from 'vue-i18n' import Cookies from 'js-cookie' import elementEnLocale from 'element-ui/lib/locale/lang/en' // element-ui lang import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN'// element-ui lang import enLocale from './en' import zhLocale from './zh' Vue.use(VueI18n) // 定義對應語言鍵,展開對應的鍵值對應表 const messages = { en: { ...enLocale, ...elementEnLocale }, zh: { ...zhLocale, ...elementZhLocale } }
其中message就是一個兩個語言(en/zh)字典下的對照表,包含各自對應鍵值下的內容。
然後整個index.js文件就是公布對應的多語言處理接口和屬性。
const i18n = new VueI18n({ locale: getLanguage(), messages }) export default i18n
然後在main.js函數裏面處理國際化的處理即可
Vue.use(ElementUI, { size: Cookies.get('size') || 'medium', // set element-ui default size i18n: (key, value) => i18n.t(key, value) }) new Vue({ el: '#app', router, store, i18n, render: h => h(App) })
有了這些準備,那麼我們在界面上就可以調用對應的鍵來獲取對應語言的語義了,
首先,我們編輯一下對應國際化的鍵值內容,例如中文參照如下所示。
例如對應登錄界面上,界面效果如下所示。
或者
其中裏面的文本內容,我們都是以國際化處理內容。
如登陸表單裏面的代碼如下所示。
<el-form ref="loginForm" :model="loginForm" :rules="rules" class="loginForm"> <el-form-item prop="username" class="login-item"> <el-input v-model="loginForm.username" class="area" type="text" :placeholder="$t('login.username')" prefix-icon="el-icon-user-solid" @keyup.enter.native="submitForm('loginForm')" /> </el-form-item> <el-form-item prop="password" class="login-item"> <el-input v-model="loginForm.password" class="area" type="password" :placeholder="$t('login.password')" prefix-icon="el-icon-lock" @keyup.enter.native="submitForm('loginForm')" /> </el-form-item> <el-form-item> <el-button :loading="loading" type="primary" class="submit_btn" @click="submitForm('loginForm')">{{ $t('login.logIn') }}</el-button> </el-form-item> <div class="tiparea"> <span style="margin-right:20px;">{{ $t('login.username') }} : admin</span> <span> {{ $t('login.password') }} : {{ $t('login.any') }}</span> </div> </el-form>
我們多處採用了類似 $t(‘login.username’) 的函數處理方式來動態獲取對應語言的內容即可,其中$t()函數裏面就是對應的語義解析的鍵參數,對應我們lang/zh.js裏面或者lang/en.js裏面的內容即可。
其中多語言切換的時候,單擊圖標就可以切換為其他語言內容了。
切換英文後界面如下所示
同樣,其他地方,如果需要切換多語言的國際化處理,也可以使用$t的轉義方式,在頂部導航欄裏面,我們可以設置得到多語言支持的界面。
中文界面提示如下所示。
這部分的實現代碼是在組件模塊裏面,一樣可以實現國際化的處理的。
<template v-if="device!=='mobile'"> <search id="header-search" class="right-menu-item" /> <error-log class="errLog-container right-menu-item hover-effect" /> <el-tooltip :content="$t('navbar.fullscreen')" effect="dark" placement="bottom"> <screenfull id="screenfull" class="right-menu-item hover-effect" /> </el-tooltip> <el-tooltip :content="$t('navbar.size')" effect="dark" placement="bottom"> <size-select id="size-select" class="right-menu-item hover-effect" /> </el-tooltip> <el-tooltip :content="$t('navbar.language')" effect="dark" placement="bottom"> <lang-select class="right-menu-item hover-effect" /> </el-tooltip> </template>
列出以下前面幾篇隨筆的連接,供參考:
循序漸進VUE+Element 前端應用開發(1)— 開發環境的準備工作
循序漸進VUE+Element 前端應用開發(2)— Vuex中的API、Store和View的使用
循序漸進VUE+Element 前端應用開發(3)— 動態菜單和路由的關聯處理
循序漸進VUE+Element 前端應用開發(4)— 獲取後端數據及產品信息頁面的處理
循序漸進VUE+Element 前端應用開發(5)— 表格列表頁面的查詢,列表展示和字段轉義處理
循序漸進VUE+Element 前端應用開發(6)— 常規Element 界面組件的使用
循序漸進VUE+Element 前端應用開發(7)— 介紹一些常規的JS處理函數
循序漸進VUE+Element 前端應用開發(8)— 樹列表組件的使用
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※超省錢租車方案
※別再煩惱如何寫文案,掌握八大原則!
※回頭車貨運收費標準
※教你寫出一流的銷售文案?
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※網頁設計最專業,超強功能平台可客製化
對異步的學習,我們先從Future開始,學習異步的實現原理。等理解了異步是怎麼實現的后,再學習Rust異步編程涉及的2個庫(futures、tokio)的時候就容易理解多了。
rust中Future的定義如下,一個Future可以理解為一段供將來調度執行的代碼。我們為什麼需要異步呢,異步相比同步高效在哪裡呢?就是異步環境下,當前調用就緒時則執行,沒有就緒時則不等待任務就緒,而是返回一個Future,等待將來任務就緒時再調度執行。當然,這裏返回Future時關鍵的是要聲明事件什麼時候就緒,就緒后怎麼喚醒這個任務到調度器去調度執行。
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[lang = "future_trait"]
pub trait Future { // A future represents an asynchronous computation.
type Output;
/* The core method of future, poll, attempts to resolve the future into a final value. This method does not block if the value is not ready. Instead, the current task is scheduled to be woken up when it's possible to make further progress by polling again. */
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>;
}
可以看到執行后的返回結果,一個是就緒返回執行結果,另一個是未就緒待定。
#[must_use = "this `Poll` may be a `Pending` variant, which should be handled"]
pub enum Poll<T> {
Ready(T),
Pending,
}
可能到這裏你還是雲里霧裡,我們寫一段代碼,幫助你理解。完整代碼見:future_study
use futures;
use std::{future::Future, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll, Waker}, thread, time::Duration};
fn main() {
// 我們現在還沒有實現調度器,所以要用一下futues庫里的一個調度器。
futures::executor::block_on(TimerFuture::new(Duration::new(10, 0)));
}
struct SharedState {
completed: bool,
waker: Option<Waker>,
}
// 我們想要實現一個定時器Future
pub struct TimerFuture {
share_state: Arc<Mutex<SharedState>>,
}
// impl Future trait for TimerFuture.
impl Future for TimerFuture {
type Output = ();
// executor will run this poll ,and Context is to tell future how to wakeup the task.
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut share_state = self.share_state.lock().unwrap();
if share_state.completed {
println!("future ready. execute poll to return.");
Poll::Ready(())
} else {
println!("future not ready, tell the future task how to wakeup to executor");
// 你要告訴future,當事件就緒后怎麼喚醒任務去調度執行,而這個waker根具體的調度器有關
// 調度器執行的時候會將上下文信息傳進來,裏面最重要的一項就是Waker
share_state.waker = Some(cx.waker().clone());
Poll::Pending
}
}
}
impl TimerFuture {
pub fn new(duration: Duration) -> Self {
let share_state = Arc::new(Mutex::new(SharedState{completed:false, waker:None}));
let thread_shared_state = share_state.clone();
thread::spawn(move || {
thread::sleep(duration);
let mut share_state = thread_shared_state.lock().unwrap();
share_state.completed = true;
if let Some(waker) = share_state.waker.take() {
println!("detect future is ready, wakeup the future task to executor.");
waker.wake() // wakeup the future task to executor.
}
});
TimerFuture {share_state}
}
}
執行結果如下:
future not ready, tell the future task how to wakeup to executor
detect future is ready, wakeup the future task to executor.
future ready. execute poll to return.
可以看到,剛開始的時候,定時10s事件還未完成,處在Pending狀態,這時要告訴這個任務後面就緒后怎麼喚醒去調度執行。等10s后,定時事件完成了,通過前面的設置的Waker,喚醒這個Future任務去調度執行。這裏,我們看一下Context和Waker是怎麼定義的:
/// The `Context` of an asynchronous task.
///
/// Currently, `Context` only serves to provide access to a `&Waker`
/// which can be used to wake the current task.
#[stable(feature = "futures_api", since = "1.36.0")]
pub struct Context<'a> {
waker: &'a Waker,
// Ensure we future-proof against variance changes by forcing
// the lifetime to be invariant (argument-position lifetimes
// are contravariant while return-position lifetimes are
// covariant).
_marker: PhantomData<fn(&'a ()) -> &'a ()>,
}
// A Waker is a handle for waking up a task by notifying its executor that it is ready to be run.
#[repr(transparent)]
#[stable(feature = "futures_api", since = "1.36.0")]
pub struct Waker {
waker: RawWaker,
}
現在你應該對Future有新的理解了,上面的代碼,我們並沒有實現調度器,而是使用的futures庫中提供的一個調度器去執行,下面自己實現一個調度器,看一下它的原理。而在Rust中,真正要用的話,還是要學習tokio庫,這裏我們只是為了講述一下實現原理,以便於理解異步是怎麼一回事。完整代碼見:future_study, 關鍵代碼如下:
use std::{future::Future, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll, Waker}, thread, time::Duration};
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
use futures::{future::{FutureExt, BoxFuture}, task::{ArcWake, waker_ref}};
use super::timefuture::*;
pub fn run_executor() {
let (executor, spawner) = new_executor_and_spawner();
// 將Future封裝成一個任務,分發到調度器去執行
spawner.spawn( async {
let v = TimerFuture::new(Duration::new(10, 0)).await;
println!("return value: {}", v);
v
});
drop(spawner);
executor.run();
}
fn new_executor_and_spawner() -> (Executor, Spawner) {
const MAX_QUEUE_TASKS: usize = 10_000;
let (task_sender, ready_queue) = sync_channel(MAX_QUEUE_TASKS);
(Executor{ready_queue}, Spawner{task_sender})
}
// executor , received ready task to execute.
struct Executor {
ready_queue: Receiver<Arc<Task>>,
}
impl Executor {
// 實際運行具體的Future任務,不斷的接收Future task執行。
fn run(&self) {
let mut count = 0;
while let Ok(task) = self.ready_queue.recv() {
count = count + 1;
println!("received task. {}", count);
let mut future_slot = task.future.lock().unwrap();
if let Some(mut future) = future_slot.take() {
let waker = waker_ref(&task);
let context = &mut Context::from_waker(&*waker);
if let Poll::Pending = future.as_mut().poll(context) {
*future_slot = Some(future);
println!("executor run the future task, but is not ready, create a future again.");
} else {
println!("executor run the future task, is ready. the future task is done.");
}
}
}
}
}
// 負責將一個Future封裝成一個Task,分發到調度器去執行。
#[derive(Clone)]
struct Spawner {
task_sender: SyncSender<Arc<Task>>,
}
impl Spawner {
// encapsul a future object to task , wakeup to executor.
fn spawn(&self, future: impl Future<Output = String> + 'static + Send) {
let future = future.boxed();
let task = Arc::new(Task {
future: Mutex::new(Some(future)),
task_sender: self.task_sender.clone(),
});
println!("first dispatch the future task to executor.");
self.task_sender.send(task).expect("too many tasks queued.");
}
}
// 等待調度執行的Future任務,這個任務必須要實現ArcWake,表明怎麼去喚醒任務去調度執行。
struct Task {
future: Mutex<Option<BoxFuture<'static, String>>>,
task_sender: SyncSender<Arc<Task>>,
}
impl ArcWake for Task {
// A way of waking up a specific task.
fn wake_by_ref(arc_self: &Arc<Self>) {
let clone = arc_self.clone();
arc_self.task_sender.send(clone).expect("too many tasks queued");
}
}
運行結果如下:
first dispatch the future task to executor.
received task. 1
future not ready, tell the future task how to wakeup to executor
executor run the future task, but is not ready, create a future again.
detect future is ready, wakeup the future task to executor.
received task. 2
future ready. execute poll to return.
return value: timer done.
executor run the future task, is ready. the future task is done.
第一次調度的時候,因為還沒有就緒,在Pending狀態,告訴這個任務,後面就緒是怎麼喚醒該任務。然後當事件就緒的時候,因為前面告訴了如何喚醒,按方法喚醒了該任務去調度執行。其實,在實際應用場景中,難的地方還在於,你怎麼知道什麼時候事件就緒,去喚醒任務,我們很容易聯想到Linux系統的epoll,tokio等底層,也是基於epoll實現的。通過epoll,我們就能方便的知道事件什麼時候就緒了。
主要學習資料如下:
上面的文章主要是學習異步的實現原理,理解異步是怎麼實現的,而進行Rust異步編程時的具體實現,則主要依賴下面2個庫:
學習這兩個庫的時候,一定要注意版本問題,這兩個庫最近變化的比較快,一定要學最新的。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計最專業,超強功能平台可客製化
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※回頭車貨運收費標準
※推薦評價好的iphone維修中心
※教你寫出一流的銷售文案?
※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!
※台中搬家公司費用怎麼算?