最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#多線程系列之線程池

 更新時(shí)間:2022年02月14日 09:22:26   作者:癡者工良  
本文詳細(xì)講解了C#多線程中的線程池,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

線程池

線程池全稱為托管線程池,線程池受 .NET 通用語言運(yùn)行時(shí)(CLR)管理,線程的生命周期由 CLR 處理,因此我們可以專注于實(shí)現(xiàn)任務(wù),而不需要理會(huì)線程管理。

線程池的應(yīng)用場(chǎng)景:任務(wù)并行庫 (TPL)操作、異步 I/O 完成、計(jì)時(shí)器回調(diào)、注冊(cè)的等待操作、使用委托的異步方法調(diào)用和套接字連接。

很多人不清楚 Task、Task<TResult> 原理,原因是沒有好好了解線程池。

ThreadPool 常用屬性和方法

屬性:

屬性說明
CompletedWorkItemCount獲取迄今為止已處理的工作項(xiàng)數(shù)。
PendingWorkItemCount獲取當(dāng)前已加入處理隊(duì)列的工作項(xiàng)數(shù)。
ThreadCount獲取當(dāng)前存在的線程池線程數(shù)。

方法:

方法說明
BindHandle(IntPtr)將操作系統(tǒng)句柄綁定到 ThreadPool。
BindHandle(SafeHandle)將操作系統(tǒng)句柄綁定到 ThreadPool。
GetAvailableThreads(Int32, Int32)檢索由 GetMaxThreads(Int32, Int32) 方法返回的最大線程池線程數(shù)和當(dāng)前活動(dòng)線程數(shù)之間的差值。
GetMaxThreads(Int32, Int32)檢索可以同時(shí)處于活動(dòng)狀態(tài)的線程池請(qǐng)求的數(shù)目。 所有大于此數(shù)目的請(qǐng)求將保持排隊(duì)狀態(tài),直到線程池線程變?yōu)榭捎谩?/td>
GetMinThreads(Int32, Int32)發(fā)出新的請(qǐng)求時(shí),在切換到管理線程創(chuàng)建和銷毀的算法之前檢索線程池按需創(chuàng)建的線程的最小數(shù)量。
QueueUserWorkItem(WaitCallback)將方法排入隊(duì)列以便執(zhí)行。 此方法在有線程池線程變得可用時(shí)執(zhí)行。
QueueUserWorkItem(WaitCallback, Object)將方法排入隊(duì)列以便執(zhí)行,并指定包含該方法所用數(shù)據(jù)的對(duì)象。 此方法在有線程池線程變得可用時(shí)執(zhí)行。
QueueUserWorkItem(Action, TState, Boolean)將 Action 委托指定的方法排入隊(duì)列以便執(zhí)行,并提供該方法使用的數(shù)據(jù)。 此方法在有線程池線程變得可用時(shí)執(zhí)行。
RegisterWaitForSingleObject(WaitHandle, WaitOrTimerCallback, Object, Int32, Boolean)注冊(cè)一個(gè)等待 WaitHandle 的委托,并指定一個(gè) 32 位有符號(hào)整數(shù)來表示超時(shí)值(以毫秒為單位)。
SetMaxThreads(Int32, Int32)設(shè)置可以同時(shí)處于活動(dòng)狀態(tài)的線程池的請(qǐng)求數(shù)目。 所有大于此數(shù)目的請(qǐng)求將保持排隊(duì)狀態(tài),直到線程池線程變?yōu)榭捎谩?/td>
SetMinThreads(Int32, Int32)發(fā)出新的請(qǐng)求時(shí),在切換到管理線程創(chuàng)建和銷毀的算法之前設(shè)置線程池按需創(chuàng)建的線程的最小數(shù)量。
UnsafeQueueNativeOverlapped(NativeOverlapped)將重疊的 I/O 操作排隊(duì)以便執(zhí)行。
UnsafeQueueUserWorkItem(IThreadPoolWorkItem, Boolean)將指定的工作項(xiàng)對(duì)象排隊(duì)到線程池。
UnsafeQueueUserWorkItem(WaitCallback, Object)將指定的委托排隊(duì)到線程池,但不會(huì)將調(diào)用堆棧傳播到輔助線程。
UnsafeRegisterWaitForSingleObject(WaitHandle, WaitOrTimerCallback, Object, Int32, Boolean)注冊(cè)一個(gè)等待 WaitHandle 的委托,并使用一個(gè) 32 位帶符號(hào)整數(shù)來表示超時(shí)時(shí)間(以毫秒為單位)。 此方法不將調(diào)用堆棧傳播到輔助線程。

線程池說明和示例

通過 System.Threading.ThreadPool 類,我們可以使用線程池。

ThreadPool 類是靜態(tài)類,它提供一個(gè)線程池,該線程池可用于執(zhí)行任務(wù)、發(fā)送工作項(xiàng)、處理異步 I/O、代表其他線程等待以及處理計(jì)時(shí)器。

理論的東西這里不會(huì)說太多,你可以參考官方文檔地址:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.threadpool?view=netcore-3.1

ThreadPool 有一個(gè) QueueUserWorkItem() 方法,該方法接受一個(gè)代表用戶異步操作的委托(名為 WaitCallback ),調(diào)用此方法傳入委托后,就會(huì)進(jìn)入線程池內(nèi)部隊(duì)列中。

WaitCallback 委托的定義如下:

public delegate void WaitCallback(object state);

現(xiàn)在我們來寫一個(gè)簡(jiǎn)單的線程池示例,再扯淡一下。

    class Program
    {
        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(MyAction);

            ThreadPool.QueueUserWorkItem(state =>
            {
                Console.WriteLine("任務(wù)已被執(zhí)行2");
            });
            Console.ReadKey();
        }
        // state 表示要傳遞的參數(shù)信息,這里為 null
        private static void MyAction(Object state)
        {
            Console.WriteLine("任務(wù)已被執(zhí)行1");
        }
    }

十分簡(jiǎn)單對(duì)不對(duì)~

這里有幾個(gè)要點(diǎn):

  • 不要將長(zhǎng)時(shí)間運(yùn)行的操作放進(jìn)線程池中;
  • 不應(yīng)該阻塞線程池中的線程;
  • 線程池中的線程都是后臺(tái)線程(又稱工作者線程);

另外,這里一定要記住 WaitCallback 這個(gè)委托。

我們觀察創(chuàng)建線程需要的時(shí)間:

        static void Main()
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            for (int i = 0; i < 10; i++)
                new Thread(() => { }).Start();
            watch.Stop();
            Console.WriteLine("創(chuàng)建 10 個(gè)線程需要花費(fèi)時(shí)間(毫秒):" + watch.ElapsedMilliseconds);
            Console.ReadKey();
        }

筆者電腦測(cè)試結(jié)果大約 160。

線程池線程數(shù)

線程池中的 SetMinThreads()和 SetMaxThreads() 可以設(shè)置線程池工作的最小和最大線程數(shù)。其定義分別如下:

// 設(shè)置線程池最小工作線程數(shù)線程
public static bool SetMinThreads (int workerThreads, int completionPortThreads);
// 獲取
public static void GetMinThreads (out int workerThreads, out int completionPortThreads);

workerThreads:要由線程池根據(jù)需要?jiǎng)?chuàng)建的新的最小工作程序線程數(shù)。

completionPortThreads:要由線程池根據(jù)需要?jiǎng)?chuàng)建的新的最小空閑異步 I/O 線程數(shù)。

SetMinThreads() 的返回值代表是否設(shè)置成功。

// 設(shè)置線程池最大工作線程數(shù)
public static bool SetMaxThreads (int workerThreads, int completionPortThreads);
// 獲取
public static void GetMaxThreads (out int workerThreads, out int completionPortThreads);

workerThreads:線程池中輔助線程的最大數(shù)目。

completionPortThreads:線程池中異步 I/O 線程的最大數(shù)目。

SetMaxThreads() 的返回值代表是否設(shè)置成功。

這里就不給出示例了,不過我們也看到了上面出現(xiàn) 異步 I/O 線程 這個(gè)關(guān)鍵詞,下面會(huì)學(xué)習(xí)到相關(guān)知識(shí)。

線程池線程數(shù)說明

關(guān)于最大最小線程數(shù),這里有一些知識(shí)需要說明。在此前,我們來寫一個(gè)示例:

    class Program
    {
        static void Main(string[] args)
        {
            // 不斷加入任務(wù)
            for (int i = 0; i < 8; i++)
                ThreadPool.QueueUserWorkItem(state =>
                {
                    Thread.Sleep(100);
                    Console.WriteLine("");
                });
            for (int i = 0; i < 8; i++)
                ThreadPool.QueueUserWorkItem(state =>
                {
                    Thread.Sleep(TimeSpan.FromSeconds(1));
                    Console.WriteLine("");
                });

            Console.WriteLine("     此計(jì)算機(jī)處理器數(shù)量:" + Environment.ProcessorCount);

            // 工作項(xiàng)、任務(wù)代表同一個(gè)意思
            Console.WriteLine("     當(dāng)前線程池存在線程數(shù):" + ThreadPool.ThreadCount);
            Console.WriteLine("     當(dāng)前已處理的工作項(xiàng)數(shù):" + ThreadPool.CompletedWorkItemCount);
            Console.WriteLine("     當(dāng)前已加入處理隊(duì)列的工作項(xiàng)數(shù):" + ThreadPool.PendingWorkItemCount);
            int count;
            int ioCount;
            ThreadPool.GetMinThreads(out count, out ioCount);
            Console.WriteLine($"     默認(rèn)最小輔助線程數(shù):{count},默認(rèn)最小異步IO線程數(shù):{ioCount}");

            ThreadPool.GetMaxThreads(out count, out ioCount);
            Console.WriteLine($"     默認(rèn)最大輔助線程數(shù):{count},默認(rèn)最大異步IO線程數(shù):{ioCount}");
            Console.ReadKey();
        }
    }

運(yùn)行后,筆者電腦輸出結(jié)果(我們的運(yùn)行結(jié)果可能不一樣):

     此計(jì)算機(jī)處理器數(shù)量:8
     當(dāng)前線程池存在線程數(shù):8
     當(dāng)前已處理的工作項(xiàng)數(shù):2
     當(dāng)前已加入處理隊(duì)列的工作項(xiàng)數(shù):8
     默認(rèn)最小輔助線程數(shù):8,默認(rèn)最小異步IO線程數(shù):8
     默認(rèn)最大輔助線程數(shù):32767,默認(rèn)最大異步IO線程數(shù):1000

我們結(jié)合運(yùn)行結(jié)果,來了解一些知識(shí)點(diǎn)。

線程池最小線程數(shù),默認(rèn)是當(dāng)前計(jì)算機(jī)處理器數(shù)量。另外我們也看到了。當(dāng)前線程池存在線程數(shù)為 8 ,因?yàn)榫€程池創(chuàng)建后,無論有沒有任務(wù),都有 8 個(gè)線程存活。

如果將線程池最小數(shù)設(shè)置得過大(SetMinThreads()),會(huì)導(dǎo)致任務(wù)切換開銷變大,消耗更多得性能資源。

如果設(shè)置得最小值小于處理器數(shù)量,則也可能會(huì)影響性能。

Environment.ProcessorCount 可以確定當(dāng)前計(jì)算機(jī)上有多少個(gè)處理器數(shù)量(例如CPU是四核八線程,結(jié)果就是八)。

SetMaxThreads() 設(shè)置的最大工作線程數(shù)或 I/O 線程數(shù),不能小于 SetMinThreads() 設(shè)置的最小工作線程數(shù)或 I/O 線程數(shù)。

設(shè)置線程數(shù)過大,會(huì)導(dǎo)致任務(wù)切換開銷變大,消耗更多得性能資源。

如果加入的任務(wù)大于設(shè)置的最大線程數(shù),那么將會(huì)進(jìn)入等待隊(duì)列。

不能將工作線程或 I/O 完成線程的最大數(shù)目設(shè)置為小于計(jì)算機(jī)上的處理器數(shù)。

不支持的線程池異步委托

扯淡了這么久,我們從設(shè)置線程數(shù)中,發(fā)現(xiàn)有個(gè) I/O 異步線程數(shù),這個(gè)線程數(shù)限制的是執(zhí)行異步委托的線程數(shù)量,這正是本節(jié)要介紹的。

異步編程模型(Asynchronous Programming Model,簡(jiǎn)稱 APM),在日常擼碼中,我們可以使用 async、await 和Task 一把梭了事。

.NET Core 不再使用 BeginInvoke 這種模式。你可以跟著筆者一起踩坑先。

筆者在看書的時(shí)候,寫了這個(gè)示例:

很多地方也在使用這種形式的示例,但是在 .NET Core 中用不了,只能在 .NET Fx 使用。。。

    class Program
    {
        private delegate string MyAsyncDelete(out int thisThreadId);
        static void Main(string[] args)
        {
            int threadId;
            // 不是異步調(diào)用
            MyMethodAsync(out threadId);

            // 創(chuàng)建自定義的委托
            MyAsyncDelete myAsync = MyMethodAsync;

            // 初始化異步的委托
            IAsyncResult result = myAsync.BeginInvoke(out threadId, null, null);

            // 當(dāng)前線程等待異步完成任務(wù),也可以去掉
            result.AsyncWaitHandle.WaitOne();
            Console.WriteLine("異步執(zhí)行");

            // 檢索異步執(zhí)行結(jié)果
            string returnValue = myAsync.EndInvoke(out threadId, result);

            // 關(guān)閉
            result.AsyncWaitHandle.Close();

            Console.WriteLine("異步處理結(jié)果:" + returnValue);
        }
        private static string MyMethodAsync(out int threadId)
        {
            // 獲取當(dāng)前線程在托管線程池的唯一標(biāo)識(shí)
            threadId = Thread.CurrentThread.ManagedThreadId;
            // 模擬工作請(qǐng)求
            Thread.Sleep(TimeSpan.FromSeconds(new Random().Next(1, 5)));
            // 返回工作完成結(jié)果
            return "喜歡我的讀者可以關(guān)注筆者的博客歐~";
        }
    }

目前百度到的很多文章也是 .NET FX 時(shí)代的代碼了,要注意 C# 在版本迭代中,對(duì)異步這些 API ,做了很多修改,不要看別人的文章,學(xué)完后才發(fā)現(xiàn)不能在 .NET Core 中使用(例如我... ...),浪費(fèi)時(shí)間。

上面這個(gè)代碼示例,也從側(cè)面說明了,以往 .NET Fx (C# 5.0 以前)中使用異步是很麻煩的。

.NET Core 是不支持異步委托的,具體可以看 https://github.com/dotnet/runtime/issues/16312

官網(wǎng)文檔明明說支持的https://docs.microsoft.com/zh-cn/dotnet/api/system.iasyncresult?view=netcore-3.1#examples,而且示例也是這樣,搞了這么久,居然不行,我等下一刀過去。

關(guān)于為什么不支持,可以看這里:https://devblogs.microsoft.com/dotnet/migrating-delegate-begininvoke-calls-for-net-core/

不支持就算了,我們跳過,后面學(xué)習(xí)異步時(shí)再仔細(xì)討論。

任務(wù)取消功能

這個(gè)取消跟線程池池?zé)o關(guān)。

CancellationToken:傳播有關(guān)應(yīng)取消操作的通知。

CancellationTokenSource:向應(yīng)該被取消的 CancellationToken 發(fā)送信號(hào)。

兩者關(guān)系如下:

        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;

這個(gè)取消,在于信號(hào)的發(fā)生和信號(hào)的捕獲,任務(wù)的取消不是實(shí)時(shí)的。

示例代碼如下:

CancellationTokenSource 實(shí)例化一個(gè)取消標(biāo)記,然后傳遞 CancellationToken 進(jìn)去;

被啟動(dòng)的線程,每個(gè)階段都判斷 .IsCancellationRequested,然后確定是否停止運(yùn)行。這取決于線程的自覺性。

    class Program
    {
        static void Main()
        {
            CancellationTokenSource cts = new CancellationTokenSource();

            Console.WriteLine("按下回車鍵,將取消任務(wù)");

            new Thread(() => { CanceTask(cts.Token); }).Start();
            new Thread(() => { CanceTask(cts.Token); }).Start();

            Console.ReadKey();
            
            // 取消執(zhí)行
            cts.Cancel();
            Console.WriteLine("完成");
            Console.ReadKey();
        }

        private static void CanceTask(CancellationToken token)
        {
            Console.WriteLine("第一階段");
            Thread.Sleep(TimeSpan.FromSeconds(1));
            if (token.IsCancellationRequested)
                return;

            Console.WriteLine("第二階段");
            Thread.Sleep(TimeSpan.FromSeconds(1));
            if (token.IsCancellationRequested)
                return;

            Console.WriteLine("第三階段");
            Thread.Sleep(TimeSpan.FromSeconds(1));
            if (token.IsCancellationRequested)
                return;

            Console.WriteLine("第四階段");
            Thread.Sleep(TimeSpan.FromSeconds(1));
            if (token.IsCancellationRequested)
                return;

            Console.WriteLine("第五階段");
            Thread.Sleep(TimeSpan.FromSeconds(1));
            if (token.IsCancellationRequested)
                return;
        }
    }

這個(gè)取消標(biāo)記,在前面的很多同步方式中,都用的上。

計(jì)時(shí)器

常用的定時(shí)器有兩種,分別是:System.Timers.Timer 和 System.Thread.Timer。

System.Threading.Timer是一個(gè)普通的計(jì)時(shí)器,它是線程池中的線程中。

System.Timers.Timer包裝了System.Threading.Timer,并提供了一些用于在特定線程上分派的其他功能。

什么線程安全不安全。。。俺不懂這個(gè)。。。不過你可以參考https://stackoverflow.com/questions/19577296/thread-safety-of-system-timers-timer-vs-system-threading-timer

如果你想認(rèn)真區(qū)分兩者的關(guān)系,可以查看:https://web.archive.org/web/20150329101415/https://msdn.microsoft.com/en-us/magazine/cc164015.aspx

兩者主要使用區(qū)別:

  • System.Timers.Timer,它會(huì)定期觸發(fā)一個(gè)事件并在一個(gè)或多個(gè)事件接收器中執(zhí)行代碼。
  • System.Threading.Timer,它定期在線程池線程上執(zhí)行一個(gè)回調(diào)方法。

大多數(shù)情況下使用 System.Threading.Timer,因?yàn)樗容^“輕”,另外就是 .NET Core 1.0 時(shí),System.Timers.Timer 被取消了,NET Core 2.0 時(shí)又回來了。主要是為了 .NET FX 和 .NET Core 遷移方便,才加上去的。所以,你懂我的意思吧。

System.Threading.Timer 其中一個(gè)構(gòu)造函數(shù)定義如下:

public Timer (System.Threading.TimerCallback callback, object state, uint dueTime, uint period);

callback:要定時(shí)執(zhí)行的方法;

state:要傳遞給線程的信息(參數(shù));

dueTime:延遲時(shí)間,避免一創(chuàng)建計(jì)時(shí)器,馬上開始執(zhí)行方法;

period:設(shè)置定時(shí)執(zhí)行方法的時(shí)間間隔;

計(jì)時(shí)器示例:

    class Program
    {
        static void Main()
        {
            Timer timer = new Timer(TimeTask,null,100,1000);
            
            Console.ReadKey();
        }

        // public delegate void TimerCallback(object? state);
        private static void TimeTask(object state)
        {
            Console.WriteLine("www.whuanle.cn");
        }
    }

Timer 有不少方法,但不常用,可以查看官方文檔:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.timer?view=netcore-3.1#methods

到此這篇關(guān)于C#多線程系列之線程池的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • C#使用NPOI讀取excel轉(zhuǎn)為DataSet

    C#使用NPOI讀取excel轉(zhuǎn)為DataSet

    這篇文章主要為大家詳細(xì)介紹了C#使用NPOI讀取excel轉(zhuǎn)為DataSet,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • 基于C#實(shí)現(xiàn)圖片滑動(dòng)驗(yàn)證碼的示例代碼

    基于C#實(shí)現(xiàn)圖片滑動(dòng)驗(yàn)證碼的示例代碼

    這篇文章主要為大家介紹了如何利用C#語言制作一個(gè)圖片滑動(dòng)驗(yàn)證碼,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2022-04-04
  • C# 調(diào)用API函數(shù)彈出映射網(wǎng)絡(luò)驅(qū)動(dòng)器對(duì)話框問題

    C# 調(diào)用API函數(shù)彈出映射網(wǎng)絡(luò)驅(qū)動(dòng)器對(duì)話框問題

    C#中的.net的常用對(duì)話框中沒有映射網(wǎng)絡(luò)驅(qū)動(dòng)映射對(duì)話框,所以需要用windows的API函數(shù)去實(shí)現(xiàn)彈出映射網(wǎng)絡(luò)驅(qū)動(dòng)器對(duì)話框
    2014-01-01
  • 利用C#驗(yàn)證哥德巴赫猜想的示例代碼

    利用C#驗(yàn)證哥德巴赫猜想的示例代碼

    哥德巴赫猜想(Goldbach’s?Conjecture)是數(shù)學(xué)領(lǐng)域的一個(gè)著名猜想,大致內(nèi)容為任何一個(gè)大于2的偶數(shù)都可以表示為兩個(gè)質(zhì)數(shù)之和,下面我們就來看看如何利用C#驗(yàn)證這一猜想呢
    2024-03-03
  • C#把整個(gè)文件內(nèi)容讀入字符串變量的方法

    C#把整個(gè)文件內(nèi)容讀入字符串變量的方法

    這篇文章主要介紹了C#把整個(gè)文件內(nèi)容讀入字符串變量的方法,實(shí)例分析了C#操作文件與字符串的相關(guān)技巧,需要的朋友可以參考下
    2015-07-07
  • 解決Unity urp級(jí)聯(lián)陰影接縫問題

    解決Unity urp級(jí)聯(lián)陰影接縫問題

    通過從unity內(nèi)部函數(shù)中抽幾個(gè)出來改造,強(qiáng)制取某個(gè)裁切球的級(jí)聯(lián)陰影映射,通過案例給大家詳細(xì)介紹,文中給出了完整的urp shader代碼,對(duì)Unity級(jí)聯(lián)陰影知識(shí)感興趣的朋友一起看看吧
    2021-06-06
  • 利用C#實(shí)現(xiàn)合并Word文檔功能

    利用C#實(shí)現(xiàn)合并Word文檔功能

    合并Word文檔可以快速地將多份編輯好的文檔合在一起,避免復(fù)制粘貼時(shí)遺漏內(nèi)容,以及耗費(fèi)不必要的時(shí)間。本文將分為以下兩部分介紹如何通過C#合并Word文檔,并附上VB.NET代碼供大家參考,希望對(duì)大家有所幫助
    2022-12-12
  • C#實(shí)現(xiàn)高性能文件批量處理器的示例代碼

    C#實(shí)現(xiàn)高性能文件批量處理器的示例代碼

    文件批量處理器可以用于數(shù)字資產(chǎn)管理,數(shù)據(jù)遷移工程,日志文件處理和安全審計(jì)場(chǎng)景,本文將使用C#開發(fā)一個(gè)高性能文件批量處理器,希望對(duì)大家有所幫助
    2025-03-03
  • C#獲取Windows10屏幕縮放比例的操作方法

    C#獲取Windows10屏幕縮放比例的操作方法

    這篇文章主要介紹了C#獲取Windows10屏幕的縮放比例,接下來介紹如何獲取Display?resolution中顯示的實(shí)際分辨率,本文通過示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-12-12
  • 如何使用C#在PDF文件添加圖片印章

    如何使用C#在PDF文件添加圖片印章

    文檔中添加印章可以起一定的作用,比如,防止文件隨意被使用,或者確保文檔內(nèi)容的安全性和權(quán)威性。C#添加圖片印章其實(shí)也有很多實(shí)現(xiàn)方法,這里我使用的是免費(fèi)的第三方軟件Free Spire.PDF,向大家闡述如何以編程的方式在PDF文件中添加圖片印章
    2017-01-01

最新評(píng)論

旺苍县| 建平县| 台州市| 晋宁县| 抚顺县| 西林县| 海伦市| 集贤县| 安化县| 永城市| 临沂市| 罗定市| 大名县| 玉屏| 襄汾县| 徐水县| 康乐县| 尼勒克县| 瑞昌市| 崇文区| 武宣县| 项城市| 长治县| 商丘市| 吉木萨尔县| 天气| 阿瓦提县| 延吉市| 定日县| 修武县| 曲麻莱县| 扶绥县| 鸡泽县| 孟津县| 绥中县| 威海市| 临高县| 嘉峪关市| 新绛县| 南昌县| 蕉岭县|