非IT從業人員,緊急求救各位大佬!

各位大佬,最近我用winform撰寫了一個利用NPOI讀取多個excel檔案的小程式,因為excel檔案有點多,就有點耗時,然后就想統計下NPOI讀取需要的時間,一開始我用form中的timer控制元件,發現觸發陳述句寫在主程式中可以觸發,但一旦寫在NPOI呼叫陳述句中時就無法觸發。我就換成了System.Timers來嘗試,發現在NPOI的陳述句中可以觸發,但是觸發后無法跨執行緒更新form中的label把時間現實出來。。。。
然后我又上網查,說跨執行緒更新UI需要用到委托,這是我的代碼
首先是添加system.timers類
System.Timers.Timer timer = new System.Timers.Timer();
timer.Enabled = true;
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
timer.AutoReset = true;
timer.Interval = 500;
timer.Enabled = true;
然后添加 Elapsed事件
public void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
t = t + 1;//得到總的毫秒數
time = GetAllTime(t);//轉化成秒表
this.SetText(time);
}
其中GetTime是一個把計時由毫秒轉化成分,秒的函式
接著是創建委托和回呼函式
}
delegate void SetTextCallback(string x);
private void SetText(string x)
{
//創建一個委托,用于封裝一個方法,在這里是封裝了 控制更新控制元件 的方法
//System.Action invokeAction = new System.Action(ChangeControlsByTimer);
//判斷操作控制元件的執行緒是否創建控制元件的執行緒
//呼叫方位于創建控制元件所在的執行緒以外的執行緒中,如果在其他執行緒則對控制元件進行方法呼叫時必須呼叫 Invoke 方法
if (this.label2.InvokeRequired)
{
//與呼叫執行緒不同的執行緒上創建(說明您必須通過 Invoke 方法對控制元件進行呼叫)
SetTextCallback d = new SetTextCallback(SetText);
this.label2.Invoke(d, new object[] {x });
}
else
{
label2.Text = x;
}
}
public static string time;
做完了這些,最后我發現再我呼叫NPOI的時候依舊無法更新UI,呼叫結束才可以更新
timer啟動我寫在了NPOI的陳述句前面
timer.Enabled = true;
for (int j = 0; j < 12; j++)
{
IWorkbook workbook = null;
FileStream fileStream = new FileStream(fileNames[j], FileMode.Open, FileAccess.Read);
if (fileNames[j].IndexOf(".xlsx") > 0)
{
//xlsx資料讀入workbook
workbook = new XSSFWorkbook(fileStream);
}
else if (fileNames[j].IndexOf(".xls") > 0)
{
//xls資料讀入workbook
workbook = new HSSFWorkbook(fileStream);
}
就是不成功,求救啊,大佬們
我是個學土木的
這幾天要弄死我了
分數都被坑人的CSDN坑光了,大佬們見諒
uj5u.com熱心網友回復:
沒有直接回答,不過統計用時不要用Timer,用System.Diagnostics.Stopwatch。uj5u.com熱心網友回復:
我查到過這個類,但是這個類可以時時計時么,我想要時時計時,還不是最后要一個總的時間
uj5u.com熱心網友回復:
有大佬么,求救啊
uj5u.com熱心網友回復:
有大佬么,求救啊
uj5u.com熱心網友回復:
因為NPOI讀取大量的Excel檔案時,會占用主執行緒,主執行緒一直被阻塞那UI就不會重繪了,所以如果需要大量的IO操作,你可以嘗試另外新建一個執行緒來進行讀取,或者嘗試學習async await方法來處理檔案操作。這樣就不會影響UI,同時你可以采用BeginInvoke來夸執行緒重繪界面uj5u.com熱心網友回復:
多執行緒同步的問題,簡單點用backgroupwork控制元件,不用自己寫同步代碼,任務完成事件回傳。高級用thread,自己加鎖,也可以用task,async 方法。uj5u.com熱心網友回復:
Timer控制元件適合做定時任務的觸發器,不適合做任務,會影響計時精度。因為Elapsed 事件是主執行緒中的呼叫的,不是Timer的計時執行緒,如果你一定要用Time,則需要在Elapsed 事件新建一個執行緒去處理任務。uj5u.com熱心網友回復:
感謝,如果我新建一個執行緒去做IO操做,又想事實記錄這段IO代碼的耗時,并實時顯示在UI中更新,應該怎么做呢,另外我總system.timer的時候,這個timer應該也不在主執行緒里吧
uj5u.com熱心網友回復:
感謝,system.timer也是占用主執行緒的嗎?不是timer控制元件
uj5u.com熱心網友回復:
感謝,我要實作實時計時的話。需要怎么做呢,timer類可以實作么
uj5u.com熱心網友回復:
差不多,都是定時觸發,在后臺起動一個執行緒,通過事件傳遞給創建這個Timer的執行緒。Timer的任務就是計時,要完成異步任務,得在事件中再開一個執行緒,這樣就影響主執行緒了。
uj5u.com熱心網友回復:
給你寫一個一遍執行阻塞任務一邊在控制臺顯示時間的 demo。你可以改輸出部分那一行代碼。static async Task testc()
{
var task1 = Task.Run(() => 顯示當前時間());
var task2 = Task.Run(() => 你的阻塞任務());
var t = await Task.WhenAny(task1, task2);
if (t == task1)
Console.WriteLine("task1完成了");
else
Console.WriteLine("task2完成了");
}
static void 顯示當前時間()
{
for (var i = 1; i < 50; ++i)
{
Console.SetCursorPosition(0, Console.CursorTop);
Console.Write($"{DateTime.Now.ToString("H:m:s.fff")} ");
Thread.Sleep(100);
}
}
static void 你的阻塞任務()
{
Thread.Sleep(6000);
}
以后我基本上都是僅在 demo 中向控制臺輸出,盡量不再寫 winform 等代碼了。因為我們的所有的服務器端產品代碼都將使用 standard 和 core(前端是 html/typescript 因此無法在這個寫),所以只能在控制臺上演示 .net 的 demo 了。
uj5u.com熱心網友回復:
使用 .net 現在的異步編程技術,那么許多“事件驅動”都可以改寫為接近順序語法的簡潔代碼了,對程式員的技術有新的要求。這里,使用 Task.WhenAny 來等待第一個并發任務執行完畢,輸出“taskN完成了”的結果。
uj5u.com熱心網友回復:
還是簡單說一下吧:1. 主執行緒應該讓它空著,這樣你的 winform 視窗才不會死掉。那么你的檔案轉換操作、時間顯示操作,如果它們不麻煩地“混合分時設計”而是各自是一個獨立操作回圈,那么就必須在主執行緒之外啟動兩個子執行緒。
2. 忘掉 Thread 物件,學習使用 Task。這里邊的知識,可以在你找作業或者招聘時,秒殺或者刷掉傳統 .net 程式員。因為現在 Task 這方面的編程很多。
3. 可以考慮一旦使用 .net core 撰寫服務器端,前端使用純 web 開發時該如何分離前后端程式進行設計和通訊。而 Excel 檔案是服務器端任務,發起任務和等待時間的顯示是前端(web)任務。
uj5u.com熱心網友回復:
現在一般都是用的Task,簡單易用uj5u.com熱心網友回復:
兩個問題:1、計時器問題,建議使用 System.Diagnostics.StopWatch,可精確到 100ns;Timer 是定時器,不是計時器,另外 Timer 有兩個(System.Windows.Forms 命名空間和 System.Timers 命名空間,這兩個定時器需要區分使用,使用不當會有問題)
2、UI 界面重繪問題,非 UI 執行緒的代碼想要呼叫 UI 界面的組件,需要使用 Invoke 方式
了解上述問題后再處理細節,可以避免走彎路。
至于上述問題的具體原理,請自行百度。
uj5u.com熱心網友回復:
兩個問題:
1、計時器問題,建議使用 System.Diagnostics.StopWatch,可精確到 100ns;Timer 是定時器,不是計時器,另外 Timer 有兩個(System.Windows.Forms 命名空間和 System.Timers 命名空間,這兩個定時器需要區分使用,使用不當會有問題)
2、UI 界面重繪問題,非 UI 執行緒的代碼想要呼叫 UI 界面的組件,需要使用 Invoke 方式
了解上述問題后再處理細節,可以避免走彎路。
至于上述問題的具體原理,請自行百度。
感謝,那請問假如我用System.Diagnostics.StopWatch來計時的話,System.Diagnostics.StopWatch這個類是運行在主執行緒上,還是他也會自己另開一個執行緒呢
uj5u.com熱心網友回復:
給你寫一個一遍執行阻塞任務一邊在控制臺顯示時間的 demo。你可以改輸出部分那一行代碼。static async Task testc()
{
var task1 = Task.Run(() => 顯示當前時間());
var task2 = Task.Run(() => 你的阻塞任務());
var t = await Task.WhenAny(task1, task2);
if (t == task1)
Console.WriteLine("task1完成了");
else
Console.WriteLine("task2完成了");
}
static void 顯示當前時間()
{
for (var i = 1; i < 50; ++i)
{
Console.SetCursorPosition(0, Console.CursorTop);
Console.Write($"{DateTime.Now.ToString("H:m:s.fff")} ");
Thread.Sleep(100);
}
}
static void 你的阻塞任務()
{
Thread.Sleep(6000);
}
以后我基本上都是僅在 demo 中向控制臺輸出,盡量不再寫 winform 等代碼了。因為我們的所有的服務器端產品代碼都將使用 standard 和 core(前端是 html/typescript 因此無法在這個寫),所以只能在控制臺上演示 .net 的 demo 了。
感謝,跨執行緒更改UI我基本明白了,那請問需要知道一段代碼運行的時間,并實時顯示,該怎么操作呢
uj5u.com熱心網友回復:
兩個問題:
1、計時器問題,建議使用 System.Diagnostics.StopWatch,可精確到 100ns;Timer 是定時器,不是計時器,另外 Timer 有兩個(System.Windows.Forms 命名空間和 System.Timers 命名空間,這兩個定時器需要區分使用,使用不當會有問題)
2、UI 界面重繪問題,非 UI 執行緒的代碼想要呼叫 UI 界面的組件,需要使用 Invoke 方式
了解上述問題后再處理細節,可以避免走彎路。
至于上述問題的具體原理,請自行百度。
如果我要在label上實時顯示計時,類似于秒表,實時重繪label,StopWatch怎么用呢
uj5u.com熱心網友回復:
兩個問題:
1、計時器問題,建議使用 System.Diagnostics.StopWatch,可精確到 100ns;Timer 是定時器,不是計時器,另外 Timer 有兩個(System.Windows.Forms 命名空間和 System.Timers 命名空間,這兩個定時器需要區分使用,使用不當會有問題)
2、UI 界面重繪問題,非 UI 執行緒的代碼想要呼叫 UI 界面的組件,需要使用 Invoke 方式
了解上述問題后再處理細節,可以避免走彎路。
至于上述問題的具體原理,請自行百度。
感謝,那請問假如我用System.Diagnostics.StopWatch來計時的話,System.Diagnostics.StopWatch這個類是運行在主執行緒上,還是他也會自己另開一個執行緒呢
System.Diagnostics.StopWatch 運行在系統底層,不在你的執行緒里(類似 CPU 時鐘,始終在跑,完全獨立于你的執行緒)。
uj5u.com熱心網友回復:
感謝,那請問假如我用System.Diagnostics.StopWatch來計時的話,System.Diagnostics.StopWatch這個類是運行在主執行緒上,還是他也會自己另開一個執行緒呢
時間關系,我沒有太仔細看你的頂樓上寫的問題。我印象中你是要在呼叫 NPOI 操作程序中去顯示一個計時跳動(過了多少分多少秒),這個就是使用
var show = Datetime.Now.Substract(startTime).ToString("m分s秒");這類代碼就行了,可以不使用 StopWatch。
uj5u.com熱心網友回復:
兩個問題:
1、計時器問題,建議使用 System.Diagnostics.StopWatch,可精確到 100ns;Timer 是定時器,不是計時器,另外 Timer 有兩個(System.Windows.Forms 命名空間和 System.Timers 命名空間,這兩個定時器需要區分使用,使用不當會有問題)
2、UI 界面重繪問題,非 UI 執行緒的代碼想要呼叫 UI 界面的組件,需要使用 Invoke 方式
了解上述問題后再處理細節,可以避免走彎路。
至于上述問題的具體原理,請自行百度。
如果我要在label上實時顯示計時,類似于秒表,實時重繪label,StopWatch怎么用呢
在回圈前獲取開始時間戳,在回圈里獲得新的時間戳,兩者相級訓取時間差,將時間差賦值給 Label 即可(如果跨執行緒,應使用 Invoke)。
uj5u.com熱心網友回復:
嗯,上邊的 ToString 的格式化寫錯了。我給你寫個完整點兒的 dmeo 吧class Session
{
public bool 是否停止;
}
static async Task testc()
{
var flag = new Session();
var task1 = Task.Run(() => 顯示當前時間(flag));
var task2 = Task.Run(() => 你的阻塞任務());
var t = await Task.WhenAny(task1, task2);
Console.WriteLine("task1完成了");
flag.是否停止 = true;
}
static void 顯示當前時間(Session s)
{
var startTime = DateTime.Now;
while (!s.是否停止)
{
Console.SetCursorPosition(0, Console.CursorTop);
var t = DateTime.Now.Subtract(startTime);
Console.Write($"{t.Minutes}分{t.Seconds}秒{t.Milliseconds}毫秒 ");
Thread.Sleep(1000);
}
Console.WriteLine("計時停止");
}
static void 你的阻塞任務()
{
Thread.Sleep(6000);
}
uj5u.com熱心網友回復:
使用 .net 現在的異步編程技術,那么許多“事件驅動”都可以改寫為接近順序語法的簡潔代碼了,對程式員的技術有新的要求。
這里,使用 Task.WhenAny 來等待第一個并發任務執行完畢,輸出“taskN完成了”的結果。
感謝大佬詳細的回復,

await我之前寫過一個小程式,想要異步通過com的方式讀取word,但是無論我怎么做,主界面還是卡,這里OpenWord1()是我寫在公共類中的一段打開word讀取資料的陳述句,不知道有什么問題
uj5u.com熱心網友回復:
使用 .net 現在的異步編程技術,那么許多“事件驅動”都可以改寫為接近順序語法的簡潔代碼了,對程式員的技術有新的要求。
這里,使用 Task.WhenAny 來等待第一個并發任務執行完畢,輸出“taskN完成了”的結果。
感謝大佬詳細的回復,
await我之前寫過一個小程式,想要異步通過com的方式讀取word,但是無論我怎么做,主界面還是卡,這里OpenWord1()是我寫在公共類中的一段打開word讀取資料的陳述句,不知道有什么問題
你這里沒有用async ,你應該再開一個專門的執行緒來處理吧,不應該讓主執行緒卡在這里
uj5u.com熱心網友回復:
微軟 Office 等 COM 組件雖然功能很強,但是是比較古老的,微軟也沒有改為 .net 等等底層機制(估計現在的也沒20年前微軟的本事去改)。這類 COM 組件絕大部分是“必須”在 UI 主執行緒呼叫的,否則可能會沒有回應、或者卡死主執行緒數分鐘、或者直接讓行程崩潰,總之 COM 的“地獄問題”很難解決。呼叫它們的時候就需要注意在 UI 主執行緒呼叫,分時呼叫,盡量使用高效率的陳述句(例如傳送一批資料給 Excel 單元格應該使用“一條”二維陣列賦值陳述句賦值而不是分別一個一個單元格賦值。使用復雜 COM 功能時跟考慮這個帖子的問題是不一樣的!轉載請註明出處,本文鏈接:https://www.uj5u.com/net/21145.html
標籤:C#
