欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#異步的世界(下)

 更新時(shí)間:2021年04月26日 15:25:06   作者:農(nóng)碼一生  
這篇文章主要介紹了C#異步的世界(下),對(duì)異步感興趣的同學(xué),可以參考下

前言

今天說異步的主要是指C#5的async\await異步。在此為了方便的表述,我們稱async\await之前的異步為“舊異步”,async\await為“新異步”。

新異步的使用

只能說新異步的使用太簡單(如果僅僅只是說使用)

方法加上async修飾符,然后使用await關(guān)鍵字執(zhí)行異步方法,即可。對(duì)就是如此簡單。像使用同步方法邏輯一樣使用異步。

public async Task<int> Test()
 {
     var num1 = await GetNumber(1);
     var num2 = await GetNumber(num1);
     var task =  GetNumber(num2);
     //或者
     var num3 = await task;
     return num1 + num2 + num3;
 }

新異步的優(yōu)勢(shì)

在此之前已經(jīng)有了多種異步模式,為什么還要引入和學(xué)習(xí)新的async\await異步呢?當(dāng)然它肯定是有其獨(dú)特的優(yōu)勢(shì)。

我們分兩個(gè)方面來分析:WinForm、WPF等單線程UI程序和Web后臺(tái)服務(wù)程序。

對(duì)于WinForm、WPF等單線程UI程序

代碼1(舊異步)

private void button1_Click(object sender, EventArgs e)
{
    var request = WebRequest.Create("https://github.com/");
    request.BeginGetResponse(new AsyncCallback(t =>
    {
        //(1)處理請(qǐng)求結(jié)果的邏輯必須寫這里
        label1.Invoke((Action)(() => { label1.Text = "[舊異步]執(zhí)行完畢!"; }));//(2)這里跨線程訪問UI需要做處理      
    }), null);
}

代碼2(同步)

private void button3_Click(object sender, EventArgs e)
{
    HttpClient http = new HttpClient();
    var htmlStr = http.GetStringAsync("https://github.com/").Result;
    //(1)處理請(qǐng)求結(jié)果的邏輯可以寫這里
    label1.Text = "[同步]執(zhí)行完畢!";//(2)不在需要做跨線程UI處理了
}

代碼3(新異步)

private async void button2_Click(object sender, EventArgs e)
 {
     HttpClient http = new HttpClient();
     var htmlStr = await http.GetStringAsync("https://github.com/");
     //(1)處理請(qǐng)求結(jié)果的邏輯可以寫這里
     label1.Text = "[新異步]執(zhí)行完畢!";//(2)不在需要做跨線程UI處理了
 }

新異步的優(yōu)勢(shì):

  • 沒有了煩人的回調(diào)處理
  • 不會(huì)像同步代碼一樣阻塞UI界面(造成假死)
  • 不在像舊異步處理后訪問UI不在需要做跨線程處理
  • 像使用同步代碼一樣使用異步(超清晰的邏輯)

是的,說得再多還不如看看實(shí)際效果圖來得實(shí)際:(新舊異步UI線程沒有阻塞,同步阻塞了UI線程)

【思考】:舊的異步模式是開啟了一個(gè)新的線程去執(zhí)行,不會(huì)阻塞UI線程。這點(diǎn)很好理解??墒?,新的異步看上去和同步區(qū)別不大,為什么也不會(huì)阻塞界面呢?

【原因】:新異步,在執(zhí)行await表達(dá)式前都是使用UI線程,await表達(dá)式后會(huì)啟用新的線程去執(zhí)行異步,直到異步執(zhí)行完成并返回結(jié)果,然后再回到UI線程(據(jù)說使用了SynchronizationContext)。所以,await是沒有阻塞UI線程的,也就不會(huì)造成界面的假死。

【注意】:我們?cè)谘菔就酱a的時(shí)候使用了Result。然,在UI單線程程序中使用Result來使異步代碼當(dāng)同步代碼使用是一件很危險(xiǎn)的事(起碼對(duì)于不太了解新異步的同學(xué)來說是這樣)。至于具體原因稍候再分析(哎呀,別跑啊)。

對(duì)于Web后臺(tái)服務(wù)程序

也許對(duì)于后臺(tái)程序的影響沒有單線程程序那么直觀,但其價(jià)值也是非常大的。且很多人對(duì)新異步存在誤解。

【誤解】:新異步可以提升Web程序的性能。

【正解】:異步不會(huì)提升單次請(qǐng)求結(jié)果的時(shí)間,但是可以提高Web程序的吞吐量。

1、為什么不會(huì)提升單次請(qǐng)求結(jié)果的時(shí)間?

其實(shí)我們從上面示例代碼(雖然是UI程序的代碼)也可以看出。

2、為什么可以提高Web程序的吞吐量?

那什么是吞吐量呢,也就是本來只能十個(gè)人同時(shí)訪問的網(wǎng)站現(xiàn)在可以二十個(gè)人同時(shí)訪問了。也就是常說的并發(fā)量。

還是用上面的代碼來解釋。[代碼2] 阻塞了UI線程等待請(qǐng)求結(jié)果,所以UI線程被占用,而[代碼3]使用了新的線程請(qǐng)求,所以UI線程沒有被占用,而可以繼續(xù)響應(yīng)UI界面。

那問題來了,我們的Web程序天生就是多線程的,且web線程都是跑的線程池線程(使用線程池線程是為了避免不斷創(chuàng)建、銷毀線程所造成的資源成本浪費(fèi)),而線程池線程可使用線程數(shù)量是一定的,盡管可以設(shè)置,但它還是會(huì)在一定范圍內(nèi)。如此一來,我們web線程是珍貴的(物以稀為貴),不能濫用。用完了,那么其他用戶請(qǐng)求的時(shí)候就無法處理直接503了。

那什么算是濫用呢?比如:文件讀取、URL請(qǐng)求、數(shù)據(jù)庫訪問等IO請(qǐng)求。如果用web線程來做這個(gè)耗時(shí)的IO操作那么就會(huì)阻塞web線程,而web線程阻塞得多了web線程池線程就不夠用了。也就達(dá)到了web程序最大訪問數(shù)。

此時(shí)我們的新異步橫空出世,解放了那些原本處理IO請(qǐng)求而阻塞的web線程(想偷懶?沒門,干活了。)。通過異步方式使用相對(duì)廉價(jià)的線程(非web線程池線程)來處理IO操作,這樣web線程池線程就可以解放出來處理更多的請(qǐng)求了。

不信?下面我們來測(cè)試下:

【測(cè)試步驟】:

1、新建一個(gè)web api項(xiàng)目

2、新建一個(gè)數(shù)據(jù)訪問類,分別提供同步、異步方法(在方法邏輯執(zhí)行前后讀取時(shí)間、線程id、web線程池線程使用數(shù))

public class GetDataHelper
{
    /// <summary>
    /// 同步方法獲取數(shù)據(jù)
    /// </summary>
    /// <returns></returns>
    public string GetData()
    {
        var beginInfo = GetBeginThreadInfo();
        using (HttpClient http = new HttpClient())
        {
            http.GetStringAsync("https://github.com/").Wait();//注意:這里是同步阻塞
        }
        return beginInfo + GetEndThreadInfo();
    }

    /// <summary>
    /// 異步方法獲取數(shù)據(jù)
    /// </summary>
    /// <returns></returns>
    public async Task<string> GetDataAsync()
    {
        var beginInfo = GetBeginThreadInfo();
        using (HttpClient http = new HttpClient())
        {
            await http.GetStringAsync("https://github.com/");//注意:這里是異步等待
        }
        return beginInfo + GetEndThreadInfo();
    }

    public string GetBeginThreadInfo()
    {
        int t1, t2, t3;
        ThreadPool.GetAvailableThreads(out t1, out t3);
        ThreadPool.GetMaxThreads(out t2, out t3);
        return string.Format("開始:{0:mm:ss,ffff} 線程Id:{1} Web線程數(shù):{2}",
                                DateTime.Now,
                                Thread.CurrentThread.ManagedThreadId,                                  
                                t2 - t1);
    }

    public string GetEndThreadInfo()
    {
        int t1, t2, t3;
        ThreadPool.GetAvailableThreads(out t1, out t3);
        ThreadPool.GetMaxThreads(out t2, out t3);
        return string.Format(" 結(jié)束:{0:mm:ss,ffff} 線程Id:{1} Web線程數(shù):{2}",
                                DateTime.Now,
                                Thread.CurrentThread.ManagedThreadId,
                                t2 - t1);
    }
}

3、新建一個(gè)web api控制器

[HttpGet]
public async Task<string> Get(string str)
{
    GetDataHelper sqlHelper = new GetDataHelper();
    switch (str)
    {
        case "異步處理"://
            return await sqlHelper.GetDataAsync();
        case "同步處理"://
            return sqlHelper.GetData();
    }
    return "參數(shù)不正確";           
}

4、發(fā)布web api程序,部署到本地iis(同步鏈接:http://localhost:803/api/Home?str=同步處理 異步鏈接:http://localhost:803/api/Home?str=異步處理)

5、接著上面的winform程序里面測(cè)試請(qǐng)求:(同時(shí)發(fā)起10個(gè)請(qǐng)求)

private void button6_Click(object sender, EventArgs e)
{
    textBox1.Text = "";
    label1.Text = "";
    Task.Run(() =>
    {
        TestResultUrl("http://localhost:803/api/Home?str=同步處理");
    });
}

private void button5_Click(object sender, EventArgs e)
{
    textBox1.Text = "";
    label1.Text = "";
    Task.Run(() =>
    {
        TestResultUrl("http://localhost:803/api/Home?str=異步處理");
    });
}

public void TestResultUrl(string url)
{
    int resultEnd = 0;
    HttpClient http = new HttpClient();

    int number = 10;
    for (int i = 0; i < number; i++)
    {
        new Thread(async () =>
        {
            var resultStr = await http.GetStringAsync(url);
            label1.Invoke((Action)(() =>
            {
                textBox1.AppendText(resultStr.Replace(" ", "\r\t") + "\r\n");
                if (++resultEnd >= number)
                {
                    label1.Text = "全部執(zhí)行完畢";
                }
            }));

        }).Start();
    }
}

6、重啟iis,并用瀏覽器訪問一次要請(qǐng)求的鏈接地址(預(yù)熱)

7、啟動(dòng)winform程序,點(diǎn)擊“訪問同步實(shí)現(xiàn)的Web”:

8、重復(fù)6,然后重新啟動(dòng)winform程序點(diǎn)擊“訪問異步實(shí)現(xiàn)的Web”

看到這些數(shù)據(jù)有什么感想?

數(shù)據(jù)和我們前面的【正解】完全吻合。仔細(xì)觀察,每個(gè)單次請(qǐng)求用時(shí)基本上相差不大。 但是步驟7"同步實(shí)現(xiàn)"最高投入web線程數(shù)是10,而步驟8“異步實(shí)現(xiàn)”最高投入web線程數(shù)是3。

也就是說“異步實(shí)現(xiàn)”使用更少的web線程完成了同樣的請(qǐng)求數(shù)量,如此一來我們就有更多剩余的web線程去處理更多用戶發(fā)起的請(qǐng)求。

接著我們還發(fā)現(xiàn)同步實(shí)現(xiàn)請(qǐng)求前后的線程ID是一致的,而異步實(shí)現(xiàn)前后線程ID不一定一致。再次證明執(zhí)行await異步前釋放了主線程。

【結(jié)論】:

  • 使用新異步可以提升Web服務(wù)程序的吞吐量
  • 對(duì)于客戶端來說,web服務(wù)的異步并不會(huì)提高客戶端的單次訪問速度。
  • 執(zhí)行新異步前會(huì)釋放web線程,而等待異步執(zhí)行完成后又回到了web線程上。從而提高web線程的利用率。

【圖解】:

Result的死鎖陷阱

我們?cè)诜治鯱I單線程程序的時(shí)候說過,要慎用異步的Result屬性。下面我們來分析:

private void button4_Click(object sender, EventArgs e)
{
    label1.Text = GetUlrString("https://github.com/").Result;
}

public async Task<string> GetUlrString(string url)
{
    using (HttpClient http = new HttpClient())
    {
        return await http.GetStringAsync(url);
    }
}

代碼GetUlrString("https://github.com/").Result的Result屬性會(huì)阻塞(占用)UI線程,而執(zhí)行到GetUlrString方法的 await異步的時(shí)候又要釋放UI線程。此時(shí)矛盾就來了,由于線程資源的搶占導(dǎo)致死鎖。

且Result屬性和.Wait()方法一樣會(huì)阻塞線程。此等問題在Web服務(wù)程序里面一樣存在。(區(qū)別:UI單次線程程序和web服務(wù)程序都會(huì)釋放主線程,不同的是Web服務(wù)線程不一定會(huì)回到原來的主線程,而UI程序一定會(huì)回到原來的UI線程)

我們前面說過,.net為什么會(huì)這么智能的自動(dòng)釋放主線程然后等待異步執(zhí)行完畢后又回到主線程是因?yàn)镾ynchronizationContext的功勞。

但這里有個(gè)例外,那就是控制臺(tái)程序里面是沒有SynchronizationContext的。所以這段代碼放在控制臺(tái)里面運(yùn)行是沒有問題的。

static void Main(string[] args)
{
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    GetUlrString("https://github.com/").Wait();
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    Console.ReadKey();
}

public async static Task<string> GetUlrString(string url)
{
    using (HttpClient http = new HttpClient())
    {
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
        return await http.GetStringAsync(url);
    }
}

打印出來的都是同一個(gè)線程ID

使用AsyncHelper在同步代碼里面調(diào)用異步

但可是,可但是,我們必須在同步方法里面執(zhí)行異步怎辦?辦法肯定是有的

我們首先定義一個(gè)AsyncHelper靜態(tài)類:

static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None,
        TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        return _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
    }
}

然后調(diào)用異步:

private void button7_Click(object sender, EventArgs e)
{
    label1.Text = AsyncHelper.RunSync(() => GetUlrString("https://github.com/"));
}

這樣就不會(huì)死鎖了。

ConfigureAwait

除了AsyncHelper我們還可以使用Task的ConfigureAwait方法來避免死鎖

private void button7_Click(object sender, EventArgs e)
{
    label1.Text = GetUlrString("https://github.com/").Result;
}

public async Task<string> GetUlrString(string url)
{
    using (HttpClient http = new HttpClient())
    {
        return await http.GetStringAsync(url).ConfigureAwait(false);
    }
}

ConfigureAwait的作用:使當(dāng)前async方法的await后續(xù)操作不需要恢復(fù)到主線程(不需要保存線程上下文)。

異常處理

關(guān)于新異步里面拋出異常的正確姿勢(shì)。我們先來看下面一段代碼:

private async void button8_Click(object sender, EventArgs e)
{
    Task<string> task = GetUlrStringErr(null);
    Thread.Sleep(1000);//一段邏輯。。。。
    textBox1.Text = await task;
}

public async Task<string> GetUlrStringErr(string url)
{
    if (string.IsNullOrWhiteSpace(url))
    {
        throw new Exception("url不能為空");
    }
    using (HttpClient http = new HttpClient())
    {
        return await http.GetStringAsync(url);
    }
}

調(diào)試執(zhí)行執(zhí)行流程:

在執(zhí)行完118行的時(shí)候竟然沒有把異常拋出來?這不是逆天了嗎。非得在等待await執(zhí)行的時(shí)候才報(bào)錯(cuò),顯然119行的邏輯執(zhí)行是沒有什么意義的。讓我們把異常提前拋出:

提取一個(gè)方法來做驗(yàn)證,這樣就能及時(shí)的拋出異常了。有朋友會(huì)說這樣的太坑爹了吧,一個(gè)驗(yàn)證還非得另外寫個(gè)方法。接下來我們提供一個(gè)沒有這么坑爹的方式:

在異步函數(shù)里面用匿名異步函數(shù)進(jìn)行包裝,同樣可以實(shí)現(xiàn)及時(shí)驗(yàn)證。

感覺也不比前種方式好多少...可是能怎么辦呢。

異步的實(shí)現(xiàn)

上面簡單分析了新異步能力和屬性。接下來讓我們繼續(xù)揭秘異步的本質(zhì),神秘的外套下面究竟是怎么實(shí)現(xiàn)的。

首先我們編寫一個(gè)用來反編譯的示例:

class MyAsyncTest
{
    public async Task<string> GetUrlStringAsync(HttpClient http, string url, int time)
    {
        await Task.Delay(time);
        return await http.GetStringAsync(url);
    }
}

反編譯代碼:

為了方便閱讀,我們把編譯器自動(dòng)命名的類型重命名。

GetUrlStringAsync方法變成了如此模樣:

public Task<string> GetUrlStringAsync(HttpClient http, string url, int time)
{
    GetUrlStringAsyncdStateMachine stateMachine = new GetUrlStringAsyncdStateMachine()
    {
        _this = this,
        http = http,
        url = url,
        time = time,
        _builder = AsyncTaskMethodBuilder<string>.Create(),
        _state = -1
    };
    stateMachine._builder.Start(ref stateMachine);
    return stateMachine._builder.Task;
}

方法簽名完全一致,只是里面的內(nèi)容變成了一個(gè)狀態(tài)機(jī)GetUrlStringAsyncdStateMachine 的調(diào)用。此狀態(tài)機(jī)就是編譯器自動(dòng)創(chuàng)建的。下面來看看神秘的狀態(tài)機(jī)是什么鬼:

private sealed class GetUrlStringAsyncdStateMachine : IAsyncStateMachine
{
    public int _state;
    public MyAsyncTest _this;
    private string _str1;
    public AsyncTaskMethodBuilder<string> _builder;
    private TaskAwaiter taskAwaiter1;
    private TaskAwaiter<string> taskAwaiter2;    //異步方法的三個(gè)形參都到這里來了
    public HttpClient http;
    public int time;
    public string url;

    private void MoveNext()
    {
        string str;
        int num = this._state;
        try
        {
            TaskAwaiter awaiter;
            MyAsyncTest.GetUrlStringAsyncdStateMachine d__;
            string str2;
            switch (num)
            {
                case 0:
                    break;

                case 1:
                    goto Label_00CD;

                default:                    //這里是異步方法 await Task.Delay(time);的具體實(shí)現(xiàn)
                    awaiter = Task.Delay(this.time).GetAwaiter();
                    if (awaiter.IsCompleted)
                    {
                        goto Label_0077;
                    }
                    this._state = num = 0;
                    this.taskAwaiter1 = awaiter;
                    d__ = this;
                    this._builder.AwaitUnsafeOnCompleted<TaskAwaiter, MyAsyncTest.GetUrlStringAsyncdStateMachine>(ref awaiter, ref d__);
                    return;
            }
            awaiter = this.taskAwaiter1;
            this.taskAwaiter1 = new TaskAwaiter();
            this._state = num = -1;
        Label_0077:
            awaiter.GetResult();
            awaiter = new TaskAwaiter();            //這里是異步方法await http.GetStringAsync(url);的具體實(shí)現(xiàn)
            TaskAwaiter<string> awaiter2 = this.http.GetStringAsync(this.url).GetAwaiter();
            if (awaiter2.IsCompleted)
            {
                goto Label_00EA;
            }
            this._state = num = 1;
            this.taskAwaiter2 = awaiter2;
            d__ = this;
            this._builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, MyAsyncTest.GetUrlStringAsyncdStateMachine>(ref awaiter2, ref d__);
            return;
        Label_00CD:
            awaiter2 = this.taskAwaiter2;
            this.taskAwaiter2 = new TaskAwaiter<string>();
            this._state = num = -1;
        Label_00EA:
            str2 = awaiter2.GetResult();
            awaiter2 = new TaskAwaiter<string>();
            this._str1 = str2;
            str = this._str1;
        }
        catch (Exception exception)
        {
            this._state = -2;
            this._builder.SetException(exception);
            return;
        }
        this._state = -2;
        this._builder.SetResult(str);
    }

    [DebuggerHidden]
    private void SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }

}

明顯多個(gè)異步等待執(zhí)行的時(shí)候就是在不斷調(diào)用狀態(tài)機(jī)中的MoveNext()方法。經(jīng)驗(yàn)來至我們之前分析過的IEumerable,不過今天的這個(gè)明顯復(fù)雜度要高于以前的那個(gè)。猜測(cè)是如此,我們還是來驗(yàn)證下事實(shí):

在起始方法GetUrlStringAsync第一次啟動(dòng)狀態(tài)機(jī)stateMachine._builder.Start(ref stateMachine);

確實(shí)是調(diào)用了MoveNext。因?yàn)開state的初始值是-1,所以執(zhí)行到了下面的位置:

繞了一圈又回到了MoveNext。由此,我們可以現(xiàn)象成多個(gè)異步調(diào)用就是在不斷執(zhí)行MoveNext直到結(jié)束。

說了這么久有什么意思呢,似乎忘記了我們的目的是要通過之前編寫的測(cè)試代碼來分析異步的執(zhí)行邏輯的。

再次貼出之前的測(cè)試代碼,以免忘記了。

反編譯后代碼執(zhí)行邏輯圖:

當(dāng)然這只是可能性較大的執(zhí)行流程,但也有awaiter.Iscompleted為true的情況。其他可能的留著大家自己去琢磨吧。

以上就是C#異步的世界(下)的詳細(xì)內(nèi)容,更多關(guān)于C#異步的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C#字符串內(nèi)存駐留機(jī)制分析

    C#字符串內(nèi)存駐留機(jī)制分析

    這篇文章介紹了C#字符串內(nèi)存駐留機(jī)制,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-01-01
  • C#操作INI配置文件示例詳解

    C#操作INI配置文件示例詳解

    這篇文章主要為大家詳細(xì)介紹了C#操作INI配置文件示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • WPF實(shí)現(xiàn)自定義窗體的示例代碼

    WPF實(shí)現(xiàn)自定義窗體的示例代碼

    .Net默認(rèn)的窗體樣式只有四種,而且都比較“丑”,但是很多時(shí)候,我們希望自定義窗體,比如,無邊框,有陰影等,所以本文為大家介紹了WPF實(shí)現(xiàn)自定義窗體的方法,希望對(duì)大家有所幫助
    2023-09-09
  • C# 設(shè)計(jì)模式系列教程-工廠方法模式

    C# 設(shè)計(jì)模式系列教程-工廠方法模式

    工廠方法去除了條件分支(解除了工廠類與分支的耦合),解決了簡單工廠對(duì)修改開放的問題。
    2016-06-06
  • 【C#基礎(chǔ)】Substring截取字符串的方法小結(jié)(推薦)

    【C#基礎(chǔ)】Substring截取字符串的方法小結(jié)(推薦)

    這篇文章主要介紹了Substring截取字符串方法小結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • C#創(chuàng)建Windows服務(wù)與服務(wù)的安裝、卸載

    C#創(chuàng)建Windows服務(wù)與服務(wù)的安裝、卸載

    這篇文章介紹了C#創(chuàng)建Windows服務(wù)與服務(wù)的安裝、卸載,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-02-02
  • Unity鍵盤WASD實(shí)現(xiàn)物體移動(dòng)

    Unity鍵盤WASD實(shí)現(xiàn)物體移動(dòng)

    這篇文章主要為大家詳細(xì)介紹了Unity鍵盤WASD實(shí)現(xiàn)物體移動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-02-02
  • C#中如何使用redis

    C#中如何使用redis

    這篇文章主要介紹了C#中如何使用redis,文中示例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • C#實(shí)現(xiàn)簡單的字符串加密

    C#實(shí)現(xiàn)簡單的字符串加密

    這篇文章介紹了C#實(shí)現(xiàn)字符串加密的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-06-06
  • 基于C#實(shí)現(xiàn)Ping工具類

    基于C#實(shí)現(xiàn)Ping工具類

    Ping是一種常用的測(cè)試網(wǎng)絡(luò)連接的工具,可以測(cè)試網(wǎng)絡(luò)延遲和連接狀況,以及判斷網(wǎng)絡(luò)是否可用,本文將通過框架類庫中的Ping類來實(shí)現(xiàn)Ping功能,感興趣的小伙伴可以了解下
    2023-11-11

最新評(píng)論