主頁 > .NET開發 > dotnet 讀 WPF 源代碼 聊聊 DispatcherTimer 的實作

dotnet 讀 WPF 源代碼 聊聊 DispatcherTimer 的實作

2021-06-09 14:53:51 .NET開發

本文來告訴大家在 WPF 框架里面,是如何實作 DispatcherTimer 的功能,有小伙伴告訴我,讀源代碼系列的博客看不動,原因是太底層了,我嘗試換一個方式切入邏輯,通過提問題和解決問題的方法,一步步告訴大家 WPF 是如何實作 DispatcherTimer 的功能

假定咱是 WPF 框架的開發者(雖然我就是,盡管是格式化代碼工程師)咱需要實作一個 DispatcherTimer 的功能,請問可以如何寫呢

在 Windows 上有很多方式來實作計時器的功能,但是 DispatcherTimer 和其他的計時器有一點不同的在于,畢竟這是 Dispatcher 的,看到 Dispatcher 就可以了解到,這是一個需要在主執行緒執行的定時器

在那么如何在定時器里面回到主執行緒呢?假定咱現在啥都沒有,畢竟咱現在是在從零開發 WPF 框架的,那有什么可以使用呢?在 Windows 上提供了 SetTimer 這個放在 User32.dll 的函式,通過這個 Win32 方法可以呼叫 Windows 提供的底層定時器的功能

寫過 Win32 代碼的小伙伴就知道,如果直接使用 Win32 的方法,無論是引數還是需要了解的知識都是非常多的,作為一個有追求的框架,咱肯定是需要再做一層封裝,讓呼叫更加簡單,回到 SetTimer 這個 Win32 函式的功能上,咱可以呼叫 SetTimer 給定一個視窗句柄以及計時的時間,接下來 Windows 將會定時發送 WM_Timer 給到咱的視窗

假定咱已經有了接收視窗訊息的統一入口,接受視窗調度的模塊的功能就是調度執行,也就是 Dispatcher 的一個功能,那不妨就將 WM_Timer 的處理也放在 Dispatcher 里面吧,剛好咱選用的 SetTimer 是發送視窗訊息,自然就是被主執行緒收到了,咱也就不需要去嘗試解決后臺執行緒的計時器需要調度到主執行緒

對于上層的 API 封裝呢?給開發者使用的計時器肯定是需要封裝一個類,那就叫 DispatcherTimer 好了,至于 DispatcherTimer 里面有哪些 API 呢,就抄 WPF 的設計好了

這里有一個問題是,假定我使用的是 DispatcherTimer 有多個,我使用其中的一個 DispatcherTimer 通過 SetTimer 這個 Win32 函式進行定時,在 Dispatcher 收到 WM_Timer 訊息時,如果知道是需要呼叫哪個 DispatcherTimer 來執行?

通過分析需求,事實上這個問題不好解決,因為 Win32 的 WM_Timer 訊息是不會告訴咱這個訊息是被哪個邏輯呼叫的 SetTimer 方法呼叫的,不能通過 WM_Timer 獲取 DispatcherTimer 物件

但是從需求分析,其實咱不需要關注收到訊息對應的是哪個 DispatcherTimer 物件,因為 DispatcherTimer 物件的功能是執行 Tick 事件,而只要是時間剛好到達,就需要執行 Tick 事件了,為了實作此功能,咱也就需要有一個集合用來管理當前主執行緒所有的 DispatcherTimer 物件,用來了解在收到 WM_Timer 需要呼叫的 DispatcherTimer 物件有哪些

這個 DispatcherTimer 集合為了方便呼叫管理,不妨先放在 Dispatcher 類里面,畢竟一個執行緒就剛好有一個 Dispatcher 物件

    public sealed class Dispatcher
    {
        private List<DispatcherTimer> _timers = new List<DispatcherTimer>();

        internal void AddTimer(DispatcherTimer timer)
        {
            lock(_instanceLock)
            {
               // 忽略代碼
               _timers.Add(timer);
            }

            // 忽略代碼
        }
    }

那在啥時候需要呼叫 AddTimer 呢?在 DispatcherTimer 物件創建的時候?如果我只是創建一個空的 DispatcherTimer 物件,這個物件啥都不干,好像加入到 Dispatcher 的 _timers 也不合適,不如就在 DispatcherTimer 啟動的時候添加

    public class DispatcherTimer
    {
        public void Start()
        {
            lock(_instanceLock)
            {
                if(!_isEnabled)
                {
                    _isEnabled = true;

                    _dispatcher.AddTimer(this);
                }
            }
        }

        private Dispatcher _dispatcher;
        private bool _isEnabled;

        // 忽略代碼
    }

在收到 WM_Timer 事件,就需要 Dispatcher 去遍歷所有的 DispatcherTimer 物件,看哪個物件當前需要被執行了,為了了解哪個 DispatcherTimer 需要被執行,就需要讓 DispatcherTimer 記錄兩個資訊,一個是距離下次執行的時間和呼叫執行 Start 函式的時間,通過判斷呼叫 Start 的時間加上距離下次執行的時間是否小于或等于當前的時間,就可以判斷當前的 DispatcherTimer 是否需要執行

咱來加一點代碼在 DispatcherTimer 里面,在啟動時記錄時間

        public void Start()
        {
            lock(_instanceLock)
            {
                if(!_isEnabled)
                {
                    _isEnabled = true;

                    _dispatcher.AddTimer(this);

                    _startTime = DateTime.Now;
                }
            }
        }

        private DateTime _startTime;
        private TimeSpan _interval;

作為一個追求性能的框架,自然咱需要在每個地方都追求一下性能,例如獲取當前時間,是否有更快的方法?通過 Environment.TickCount 屬性可以獲取更快的時間,使用 Environment.TickCount 獲取的是毫秒數,表示的是開機到當前的時間,相對來說抽象一點,不過也剛好不會受到用戶修改當前系統時間的影響,自然也就更穩定一些啦

既然都使用 Environment.TickCount 了,不如將 判斷呼叫 Start 的時間加上距離下次執行的時間 合在一起計算吧,這樣后續每次 WM_Timer 訊息過來的時候,就不用每次都做一次加法了,直接判斷值的大小即可

        public void Start()
        {
            lock(_instanceLock)
            {
                if(!_isEnabled)
                {
                    _isEnabled = true;

                    _dispatcher.AddTimer(this);

                    // 如果只是記錄當前呼叫 Start 方法的時間,也就是 Environment.TickCount 時間,那么后續收到 WM_Timer 訊息,都需要判斷當前時間加上 _interval 的時間之后是否小于等于當前的時間,而這個加法計算是每次都需要呼叫的,為了性能優化,不如一開始就加上,后續就只需要判斷大小
                    _dueTimeInTicks = Environment.TickCount + (int) _interval.TotalMilliseconds;
                }
            }
        }

        // 洗掉 DateTime 的定義,因為獲取的性能不夠,而且用戶也許修改系統時間
        // private DateTime _startTime;
        private TimeSpan _interval;

        // 用這個代替 DateTime 的方法,單位是毫秒,其實欄位從規范來說是不應該 internal 公開的,然而在 WPF 里面,古老的開發者為了減少改動就公開了這個欄位
        internal int _dueTimeInTicks; // used by Dispatcher

在 Dispatcher 里面就可以通過 DispatcherTimer 的 _dueTimeInTicks 欄位和當前的時間比較大小而決定是否觸發 DispatcherTimer 的事件,從規范的角度來說,是不能公開 DispatcherTimer 的 _dueTimeInTicks 欄位的,然而在 WPF 里面,古老的開發者為了減少改動就公開了這個欄位

在 Dispatcher 里面的代碼如下

    public sealed class Dispatcher
    {
        private List<DispatcherTimer> _timers = new List<DispatcherTimer>();

        private IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
        	WindowMessage message = (WindowMessage) msg;

            // 忽略代碼
        	if(message == WindowMessage.WM_TIMER && (int) wParam == TIMERID_TIMERS)
            {
            	// 忽略代碼
                PromoteTimers(Environment.TickCount);
            }
        }

        internal void PromoteTimers(int currentTimeInTicks)
        {
                    DispatcherTimer timer = null;
                    int iTimer = 0;
                    var timersVersion = _timersVersion;

                    do
                    {
                        lock(_instanceLock)
                        {
                            timer = null;

                            // If the timers collection changed while we are in the middle of
                            // looking for timers, start over.
                            if(timersVersion != _timersVersion)
                            {
                            	// 如果在回圈程序,有其他邏輯加入了 _timers 的元素,意味著 _timers 的數量變更了
                            	// 需要重新開始
                                timersVersion = _timersVersion;
                                iTimer = 0;
                            }

                            while(iTimer < _timers.Count)
                            {
                                // WARNING: this is vulnerable to wrapping
                                if(_timers[iTimer]._dueTimeInTicks - currentTimeInTicks <= 0)
                                {
                                    timer = _timers[iTimer];

                                    // 忽略代碼
                                    break;
                                }
                                else
                                {
                                    iTimer++;
                                }
                            }
                        }

                        // Now that we are outside of the lock, promote the timer.
                        if(timer != null)
                        {
                            timer.Promote();
                        }
                    } while(timer != null);
        }
    }

以上判斷是通過 _timers[iTimer]._dueTimeInTicks - currentTimeInTicks <= 0 決定是否當前的 Timer 需要執行,因為 _timers[iTimer]._dueTimeInTicks - currentTimeInTicks <= 0 等價于 _timers[iTimer]._dueTimeInTicks <= currentTimeInTicks 也就是在 DispatcherTimer 下次執行的時間,小于或等于當前的時間,這個 DispatcherTimer 就應該被執行,因為相同的時間需要執行的 DispatcherTimer 也許有多個,因此就做了兩重回圈,而同時為了解決在 DispatcherTimer 執行程序,也許有其他邏輯再加入新的 DispatcherTimer 因此也就需要判斷一下 _timersVersion 當前版本適合和進入的版本相同,如果不同,就證明有其他邏輯更改了集合,需要重新開始

從上面代碼可以看到,咱判斷 DispatcherTimer 是否需要被執行,如果需要執行,呼叫 DispatcherTimer 的 Promote 方法進行執行,最簡單的方法執行就是通過呼叫 Tick 事件觸發,簡單的代碼如下

        private void FireTick()
        {
            // 忽略代碼
            if(Tick != null)
            {
                Tick(this, EventArgs.Empty);
            }
        }

        internal void Promote() // called from Dispatcher
        {
            FireTick();
        }

既然所有的 DispatcherTimer 都被 Dispatcher 放在一起,那是否可以共用一個 Win32 的計時器,不需要每個 DispatcherTimer 都獨立呼叫,如上面的代碼,其實都是在判斷統一的時間,不需要多個 Win32 計時器也能實作效果

只需要有一個 Win32 計時器,定時是當前的 DispatcherTimer 里面最短的時間,就可以實作多個 DispatcherTimer 使用相同的一個 Win32 計時器,那這個邏輯可以放在哪呢?是否還記得咱在啟動計時器時加入到 Dispatcher 里面,既然咱期望多個 DispatcherTimer 使用相同的一個 Win32 計時器,不妨找到一對多的關系,剛好這里的一就是 Dispatcher 類,這里的多就是 DispatcherTimer 類, 因此這個 Win32 計時器的管理,放在 Dispatcher 里面就剛好,啟動或者重新設定 Win32 計時器可以放在 Dispatcher 的 AddTimer 方法里面


    public sealed class Dispatcher
    {
        private List<DispatcherTimer> _timers = new List<DispatcherTimer>();
        private long _timersVersion;

        internal void AddTimer(DispatcherTimer timer)
        {
            lock(_instanceLock)
            {
                _timers.Add(timer);
                _timersVersion++;
            }
            UpdateWin32Timer();
        }
    }

在加入 AddTimer 呼叫 UpdateWin32Timer 更新計時器時間,原因是如果我原有一個是定時是 10 秒的計時器在啟動了,接下來運行了 5 秒,我再加入一個需要等 1 秒的計時器,那么原有的 Win32 計時器是不是就需要更新一下時間?從原來的等待 10 秒,判斷距離現在還有 5 秒才執行,而新加入的等待 1 秒的計時器,在接下來的 1 秒就需要執行,那么就需要更新 Win32 計時器,修改定時時間

而如果原有一個是定時是 10 秒的計時器在啟動了,接下來運行了 9 秒,我再加入一個需要等 3 秒的計時器,顯然新加入的計時器還需要等待 3 秒才執行,而原有的計時器,只需要再等待 1 秒就足夠 10 秒了,可以執行,此時的 Win32 計時器自然是不需要重新啟動的

似乎上面的邏輯稍微有一點繞,但是看起來代碼也是很簡單的

    public sealed class Dispatcher
    {
        private int _dueTimeInTicks;
    	private bool _dueTimeFound;

        internal void UpdateWin32Timer() // Called from DispatcherTimer
        {
                    bool oldDueTimeFound = _dueTimeFound;
                    int oldDueTimeInTicks = _dueTimeInTicks;
                    _dueTimeFound = false;
                    _dueTimeInTicks = 0;

                    if(_timers.Count > 0)
                    {
                        // We could do better if we sorted the list of timers.
                        for(int i = 0; i < _timers.Count; i++)
                        {
                            DispatcherTimer timer = _timers[i];

                            if(!_dueTimeFound || timer._dueTimeInTicks - _dueTimeInTicks < 0)
                            {
                                _dueTimeFound = true;
                                _dueTimeInTicks = timer._dueTimeInTicks;
                            }
                        }
                    }

                    if(_dueTimeFound)
                    {
                        if(!_isWin32TimerSet || !oldDueTimeFound || (oldDueTimeInTicks != _dueTimeInTicks))
                        {
                            SetWin32Timer(_dueTimeInTicks);
                        }
                    }
                    else if(oldDueTimeFound)
                    {
                        KillWin32Timer();
                    }
        }
    }

大概這樣就算完成了 DispatcherTimer 的核心實作了,不過此時讓咱去天臺將性能優化組救下,性能優化組說如果有連續的多個 DispatcherTimer 在執行,此時界面上就卡不動了,因為咱上面的代碼,多個 DispatcherTimer 執行之間是沒有切換調度的,也就是說剛好有多個 DispatcherTimer 都在執行,那么主執行緒的資源都在去處理其他業務邏輯里,沒有資源去處理界面渲染等

產品大佬也加了需求,要求在 DispatcherTimer 可以加入優先級,優先級相等于 Dispatcher 的優先級,于是咱的邏輯代碼也需要改改

在 DispatcherTimer 的 Promote 方法里面,看起來不能呼叫 FireTick 開始執行代碼邏輯,而是需要有優先級調度,也需要有切換調度,不能將全部的 DispatcherTimer 一次性執行,最簡單的方法自然就是 Dispatcher.InvokeAsync 等方法來實作優先級調度等功能

產品大佬的需求實作了,但性能優化組還在天臺上,咱還需要再優化一下,既然都將 DispatcherTimer 加入到 Dispatcher 里面了,那為什么還需要 Dispatcher.InvokeAsync 調度呢?最簡單的方法就是在 DispatcherTimer 啟動的時候,將任務加入到 Dispatcher 里面,但是設定優先級為不執行,當 DispatcherTimer 的 Promote 呼叫時,設定剛才的加入的任務的優先級為 DispatcherTimer 的執行優先級,自然就會被 Dispatcher 進行調度了

    public class DispatcherTimer
    {
        public DispatcherTimer(DispatcherPriority priority) // NOTE: should be Priority
        {
            _priority = priority;
        }

        private void Start()
        {
            lock(_instanceLock)
            {
                if (_operation != null)
                {
                    // Timer has already been restarted, e.g. Start was called form the Tick handler.
                    return;
                }

                // BeginInvoke a new operation.
                _operation = _dispatcher.BeginInvoke(
                    DispatcherPriority.Inactive,
                    new DispatcherOperationCallback(FireTick),
                    null);

                
                _dueTimeInTicks = Environment.TickCount + (int) _interval.TotalMilliseconds;
                
                _dispatcher.AddTimer(this);
            }
} // 這確實是 WPF 的格式化,這是花括號前面沒有空格

        internal void Promote() // called from Dispatcher
        {
            lock(_instanceLock)
            {
                // Simply promote the operation to it's desired priority.
                if(_operation != null)
                {
                    _operation.Priority = _priority;
                }
            }
        }

        private object FireTick(object unused)
        {
            if(Tick != null)
            {
                Tick(this, EventArgs.Empty);
            }

            return null;
        }

        private DispatcherPriority _priority;  // NOTE: should be Priority
    }

通過上面的代碼,性能優化組從天臺上下來了,但產品大佬又說,有一些用戶喜歡在 Tick 里面里面將 DispatcherTimer 停下,而以上的代碼,其實咱沒有實作停下的功能,剛好兩個功能一起做

在 DispatcherTimer 里面定義 IsEnabled 屬性,咱需要支持在 IsEnabled 里面進行賦值從而進行停止或啟動計時器

    public class DispatcherTimer
    {
        /// <summary>
        ///     Gets or sets whether the timer is running.
        /// </summary>
        public bool IsEnabled
        {
            get
            {
                return _isEnabled;
            }

            set
            {
                lock(_instanceLock)
                {
                    if(!value && _isEnabled)
                    {
                        Stop();
                    }
                    else if(value && !_isEnabled)
                    {
                        Start();
                    }
                }
            }
        }

        private bool _isEnabled;
    }

既然有不斷的開啟和停止,那不如就再加一個 Restart 方法好了,讓 Start 方法呼叫 Restart 方法

    public class DispatcherTimer
    {
        public void Start()
        {
            lock(_instanceLock)
            {
                if(!_isEnabled)
                {
                    _isEnabled = true;

                    Restart();
                }
            }
        }

        private void Restart()
        {
            lock(_instanceLock)
            {
                if (_operation != null)
                {
                    // Timer has already been restarted, e.g. Start was called form the Tick handler.
                    return;
                }

                // BeginInvoke a new operation.
                _operation = _dispatcher.BeginInvoke(
                    DispatcherPriority.Inactive,
                    new DispatcherOperationCallback(FireTick),
                    null);

                
                _dueTimeInTicks = Environment.TickCount + (int) _interval.TotalMilliseconds;
                
                _dispatcher.AddTimer(this);
            }
}
    }

那 Stop 方法呢?其實就是從 Dispatcher 佇列里面干掉 _operation 物件

    public class DispatcherTimer
    {
        public void Stop()
        {
              if(_operation != null)
              {
                   _operation.Abort();
                   _operation = null;
              }
        }
    }

當然了,如果當前計時最短就是當前的被 Stop 的 DispatcherTimer 那還需要更新一下 Win32 的計時器時間,例如當前已設定了最短的計時是 1 秒的 DispatcherTimer 被 Stop 了,而后續的 DispatcherTimer 是再等 5 秒,此時就需要修改 Win32 的計時器,關閉等待 1 秒的計時器,再開啟等待 5 秒的計時器,另外咱將 DispatcherTimer 加入到 Dispatcher 的一個集合里面,自然就需要在 Stop 里面移除,否則將會讓 DispatcherTimer 物件無法釋放

咱更改 Stop 方法,加上告訴 Dispatcher 的方法

    public class DispatcherTimer
    {
        public void Stop()
        {
            bool updateWin32Timer = false;
            
            lock(_instanceLock)
            {
                if(_isEnabled)
                {
                    _isEnabled = false;
                    updateWin32Timer = true;

                    // If the operation is in the queue, abort it.
                    if(_operation != null)
                    {
                        _operation.Abort();
                        _operation = null;
                    }
}
            }

            if(updateWin32Timer)
            {
                _dispatcher.RemoveTimer(this);
            }
        }
    }

在 Dispatcher 的里面,先從集合里面將 DispatcherTimer 移除,當然,從這里也可以看到,即使在業務代碼里面沒有對 DispatcherTimer 進行參考,但是只要這個 DispatcherTimer 還在運行,那么 DispatcherTimer 的物件就不會被釋放,接著在 Dispatcher 更新計時器

    public sealed class Dispatcher
    {
        internal void RemoveTimer(DispatcherTimer timer)
        {
            lock(_instanceLock)
            {
                if(!_hasShutdownFinished) // Could be a non-dispatcher thread, lock to read
                {
                    _timers.Remove(timer);
                    _timersVersion++;
                }
            }
            UpdateWin32Timer();
        }
    }

再接下來,產品大佬告訴咱,加需求,可以讓開發者修改 DispatcherTimer 的計時時間,在修改 Interval 屬性時,需要咱自己去更新 Dispatcher 的計時器

在 IsEnabled 開啟時,如果用戶修改 Interval 屬性,那么需要告訴 Dispatcher 更新計時器,而如果沒有開啟計時器,那更新 Dispatcher 做什么

    public class DispatcherTimer
    {
        /// <summary>
        ///     Gets or sets the time between timer ticks.
        /// </summary>
        public TimeSpan Interval
        {
            get
            {
                return _interval;
            }

            set
            {
                bool updateWin32Timer = false;

                lock(_instanceLock)
                {
                    _interval = value;

                    if(_isEnabled)
                    {
                        _dueTimeInTicks = Environment.TickCount + (int)_interval.TotalMilliseconds;
                        updateWin32Timer = true;
                    }
                }

                if(updateWin32Timer)
                {
                    _dispatcher.UpdateWin32Timer();
                }
            }
        }

        private TimeSpan _interval;
    }

當然了,作為對外公開的 API 還需要判斷一下調皮的用戶的行為,如果傳入的時間是負數呢?如果傳入的時間太長了,例如超過 int 的 MaxValue 也就是說這個 DispatcherTimer 是不執行的吧,是不是就需要告訴用戶

        public TimeSpan Interval
        {
            set
            {
                bool updateWin32Timer = false;
                
                if (value.TotalMilliseconds < 0)
                    throw new ArgumentOutOfRangeException("value", SR.Get(SRID.TimeSpanPeriodOutOfRange_TooSmall));

                if (value.TotalMilliseconds > Int32.MaxValue)
                    throw new ArgumentOutOfRangeException("value", SR.Get(SRID.TimeSpanPeriodOutOfRange_TooLarge));

                // 忽略代碼
            }
        }

產品大佬還說,咱的 DispatcherTimer 是允許在后臺執行緒啟動的,畢竟不想讓用戶需要寫 Dispatcher 調度到主執行緒再開啟 DispatcherTimer 計時,允許在后臺執行緒開啟,如上面代碼,其實咱加了很多鎖了,問題也不大,這部分邏輯實作太簡單了,這里就不告訴大家了

以上大概就是 DispatcherTimer 的核心邏輯,可以看到 DispatcherTimer 里面的細節還是很多的,實際的 WPF 代碼里面也有很多細節部分是本文沒有告訴大家的,還請大家自己去閱讀 WPF 源代碼

更多 DispatcherTimer 請看: WPF 如何知道當前有多少個 DispatcherTimer 在運行

當前的 WPF 在 https://github.com/dotnet/wpf 完全開源,使用友好的 MIT 協議,意味著允許任何人任何組織和企業任意處置,包括使用,復制,修改,合并,發表,分發,再授權,或者銷售,在倉庫里面包含了完全的構建邏輯,只需要本地的網路足夠好(因為需要下載一堆構建工具),即可進行本地構建

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/285392.html

標籤:.NET技术

上一篇:使用ef core的遷移功能并配置種子資料

下一篇:WPF實作音樂字幕影片

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more