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

C#中async/await之線程上下文工作原理

 更新時(shí)間:2023年05月31日 14:23:20   作者:微軟技術(shù)棧  
這篇文章主要為大家介紹了C#中async/await之線程上下文工作原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>

引言

接《async/await 在 C# 語言中是如何工作的?(上)》、《async/await 在 C# 語言中是如何工作的?(中)》,今天我們繼續(xù)介紹 SynchronizationContext 和 ConfigureAwait。

SynchronizationContext 和 ConfigureAwait

我們之前在 EAP 模式的上下文中討論過 SynchronizationContext,并提到它將再次出現(xiàn)。SynchronizationContext 使得調(diào)用可重用的輔助函數(shù)成為可能,并自動(dòng)被調(diào)度回調(diào)用環(huán)境認(rèn)為合適的任何地方。因此,我們很自然地認(rèn)為 async/await 能“正常工作”,事實(shí)也的確如此?;氐角懊娴陌粹o單擊處理程序:

ThreadPool.QueueUserWorkItem(_ =>
{
    string message = ComputeMessage();
    button1.BeginInvoke(() =>
    {
        button1.Text = message;
    });
});

使用 async/await,我們可以這樣寫:

button1.Text = await Task.Run(() => ComputeMessage());

對 ComputeMessage 的調(diào)用被轉(zhuǎn)移到線程池中,這個(gè)方法執(zhí)行完畢后,執(zhí)行又轉(zhuǎn)移回與按鈕關(guān)聯(lián)的 UI 線程,設(shè)置按鈕的 Text 屬性就是在這個(gè)線程中進(jìn)行的。

與 SynchronizationContext 的集成由 awaiter 實(shí)現(xiàn)(為狀態(tài)機(jī)生成的代碼對 SynchronizationContext 一無所知),因?yàn)楫?dāng)所表示的異步操作完成時(shí),是 awaiter 負(fù)責(zé)實(shí)際調(diào)用或?qū)⑺峁┑?continuation 排隊(duì)。而自定義 awaiter 不需要考慮 SynchronizationContext。目前,Task、Task<TResult>、ValueTask、ValueTask<TResult> 的等待器都是 do。這意味著,默認(rèn)情況下,當(dāng)你等待一個(gè)任務(wù),一個(gè) Task<TResult>,一個(gè) ValueTask,一個(gè) ValueTask<TResult>,甚至 Task. yield() 調(diào)用的結(jié)果時(shí),awaiter 默認(rèn)會(huì)查找當(dāng)前的 SynchronizationContext,如果它成功地獲得了一個(gè)非默認(rèn)的同步上下文,最終會(huì)將 continuation 排隊(duì)到該上下文。

如果我們查看 TaskAwaiter 中涉及的代碼,就可以看到這一點(diǎn)。以下是 Corelib 中的相關(guān)代碼片段:

internal void UnsafeSetContinuationForAwait(IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
{
    if (continueOnCapturedContext)
    {
        SynchronizationContext? syncCtx = SynchronizationContext.Current;
        if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))
        {
            var tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, stateMachineBox.MoveNextAction, flowExecutionContext: false);
            if (!AddTaskContinuation(tc, addBeforeOthers: false))
            {
                tc.Run(this, canInlineContinuationTask: false);
            }
            return;
        }
        else
        {
            TaskScheduler? scheduler = TaskScheduler.InternalCurrent;
            if (scheduler != null && scheduler != TaskScheduler.Default)
            {
                var tc = new TaskSchedulerAwaitTaskContinuation(scheduler, stateMachineBox.MoveNextAction, flowExecutionContext: false);
                if (!AddTaskContinuation(tc, addBeforeOthers: false))
                {
                    tc.Run(this, canInlineContinuationTask: false);
                }
                return;
            }
        }
    }
    ...
}

這是一個(gè)方法的一部分,用于確定將哪個(gè)對象作為 continuation 存儲(chǔ)到任務(wù)中。它被傳遞給 stateMachineBox,如前所述,它可以直接存儲(chǔ)到任務(wù)的 continuation 列表中。但是,這個(gè)特殊的邏輯可能會(huì)將 IAsyncStateMachineBox 封裝起來,以合并一個(gè)調(diào)度程序(如果存在的話)。它檢查當(dāng)前是否有非默認(rèn)的 SynchronizationContext,如果有,它會(huì)創(chuàng)建一個(gè) SynchronizationContextAwaitTaskContinuation 作為實(shí)際的對象,它會(huì)被存儲(chǔ)為 continuation;

該對象依次包裝了原始的和捕獲的 SynchronizationContext,并知道如何在與后者排隊(duì)的工作項(xiàng)中調(diào)用前者的 MoveNext。這就是如何在 UI 應(yīng)用程序中作為事件處理程序的一部分等待,并在等待完成后讓代碼繼續(xù)在正確的線程上運(yùn)行。這里要注意的下一個(gè)有趣的事情是,它不僅僅關(guān)注一個(gè) SynchronizationContext:如果它找不到一個(gè)自定義的 SynchronizationContext 來使用,它還會(huì)查看 Tasks 使用的 TaskScheduler 類型是否有一個(gè)需要考慮的自定義類型。和 SynchronizationContext 一樣,如果有一個(gè)非默認(rèn)值,它就會(huì)和原始框一起包裝在 TaskSchedulerAwaitTaskContinuation 中,用作 continuation 對象。

但這里最值得注意的可能是方法主體的第一行:if (continueOnCapturedContext)。我們只在 continueOnCapturedContext 為 true 時(shí)才對 SynchronizationContext/TaskScheduler 進(jìn)行這些檢查;如果這個(gè)值為 false,實(shí)現(xiàn)方式就好像兩者都是默認(rèn)值一樣,會(huì)忽略它們。請問是什么將 continueOnCapturedContext 設(shè)置為 false?你可能已經(jīng)猜到了:使用非常流行的 ConfigureAwait(false)。

可以這樣說,作為 await 的一部分,ConfigureAwait(false) 做的唯一一件事是將它的參數(shù)布爾值作為 continueOnCapturedContext 值提供給這個(gè)函數(shù)(以及其他類似的函數(shù)),以便跳過對 SynchronizationContext/TaskScheduler 的檢查,表現(xiàn)得好像它們都不存在一樣。對于進(jìn)程來說,這允許 Task 在它認(rèn)為合適的地方調(diào)用其 continuation,而不是強(qiáng)制將它們排隊(duì)在某個(gè)特定的調(diào)度器上執(zhí)行。

我之前提到過 SynchronizationContext 的另一個(gè)方面,我說過我們會(huì)再次看到它:OperationStarted/OperationCompleted?,F(xiàn)在是時(shí)候了。這是沒那么受歡迎的特性:異步 void。除了 configureawait 之外,async void 可以說是 async/await 中最具爭議性的特性之一。它被添加的原因只有一個(gè):事件處理程序。在 UI 應(yīng)用程序中,你可以編寫如下代碼:

button1.Click += async (sender, eventArgs) =>
{
  button1.Text = await Task.Run(() => ComputeMessage());  
};

但如果所有的異步方法都必須有一個(gè)像 Task 這樣的返回類型,你就不能這樣做了。Click 事件有一個(gè)簽名 public event EventHandler? Click;,其中 EventHandler 定義為 public delegate void EventHandler(object? sender, EventArgs e);,因此要提供一個(gè)符合該簽名的方法,該方法需要是 void-returning。

有各種各樣的理由認(rèn)為 async void 是不好的,為什么文章建議盡可能避免使用它,以及為什么出現(xiàn)了各種 analyzers 來標(biāo)記使用 async void。最大的問題之一是委托推理??紤]下面的程序:

using System.Diagnostics;
Time(async () =>
{
    Console.WriteLine("Enter");
    await Task.Delay(TimeSpan.FromSeconds(10));
    Console.WriteLine("Exit");
});
static void Time(Action action)
{
    Console.WriteLine("Timing...");
    Stopwatch sw = Stopwatch.StartNew();
    action();
    Console.WriteLine($"...done timing: {sw.Elapsed}");
}

人們很容易期望它輸出至少10秒的運(yùn)行時(shí)間,但如果你運(yùn)行它,你會(huì)發(fā)現(xiàn)輸出是這樣的:

Timing...
Enter
...done timing: 00:00:00.0037550

async lambda 實(shí)際上是一個(gè)異步 void 方法。異步方法會(huì)在遇到第一個(gè)暫停點(diǎn)時(shí)返回調(diào)用者。如果這是一個(gè)異步 Task 方法,Task 就會(huì)在這個(gè)時(shí)間點(diǎn)返回。但對于 async void,什么都不會(huì)返回。Time 方法只知道它調(diào)用了 action();委托調(diào)用返回;它不知道 async 方法實(shí)際上仍在“運(yùn)行”,并將在稍后異步完成。

這就是 OperationStarted/OperationCompleted 的作用。這種異步 void 方法本質(zhì)上與前面討論的 EAP 方法類似:這種方法的初始化是 void,因此需要一些其他機(jī)制來跟蹤所有此類操作。因此,EAP 實(shí)現(xiàn)在操作啟動(dòng)時(shí)調(diào)用當(dāng)前 SynchronizationContext 的 OperationStarted,在操作完成時(shí)調(diào)用 OperationCompleted,async void 也做同樣的事情。與 async void 相關(guān)的構(gòu)建器是 AsyncVoidMethodBuilder。還記得在 async 方法的入口,編譯器生成的代碼如何調(diào)用構(gòu)建器的靜態(tài) Create 方法來獲得適當(dāng)?shù)臉?gòu)建器實(shí)例嗎?AsyncVoidMethodBuilder 利用了這一點(diǎn)來掛鉤創(chuàng)建和調(diào)用 OperationStarted:

public static AsyncVoidMethodBuilder Create()
{
    SynchronizationContext? sc = SynchronizationContext.Current;
    sc?.OperationStarted();
    return new AsyncVoidMethodBuilder() { _synchronizationContext = sc };
}

類似地,當(dāng)通過 SetResult 或 SetException 將構(gòu)建器標(biāo)記為完成時(shí),它會(huì)調(diào)用相應(yīng)的 OperationCompleted 方法。這就是像 xunit 這樣的單元測試框架如何能夠具有異步 void 測試方法,并仍然在并發(fā)測試執(zhí)行中使用最大程度的并發(fā),例如在 xunit 的 AsyncTestSyncContext 中。

有了這些知識(shí),現(xiàn)在可以重寫我們的 timing 示例:

using System.Diagnostics;
Time(async () =>
{
    Console.WriteLine("Enter");
    await Task.Delay(TimeSpan.FromSeconds(10));
    Console.WriteLine("Exit");
});
static void Time(Action action)
{
    var oldCtx = SynchronizationContext.Current;
    try
    {
        var newCtx = new CountdownContext();
        SynchronizationContext.SetSynchronizationContext(newCtx);
        Console.WriteLine("Timing...");
        Stopwatch sw = Stopwatch.StartNew();
        action();
        newCtx.SignalAndWait();
        Console.WriteLine($"...done timing: {sw.Elapsed}");
    }
    finally
    {
        SynchronizationContext.SetSynchronizationContext(oldCtx);
    }
}
sealed class CountdownContext : SynchronizationContext
{
    private readonly ManualResetEventSlim _mres = new ManualResetEventSlim(false);
    private int _remaining = 1;
    public override void OperationStarted() => Interlocked.Increment(ref _remaining);
    public override void OperationCompleted()
    {
        if (Interlocked.Decrement(ref _remaining) == 0)
        {
            _mres.Set();
        }
    }
    public void SignalAndWait()
    {
        OperationCompleted();
        _mres.Wait();
    }
}

在這里,我已經(jīng)創(chuàng)建了一個(gè) SynchronizationContext,它跟蹤了一個(gè)待定操作的計(jì)數(shù),并支持阻塞等待它們?nèi)客瓿?。?dāng)我運(yùn)行它時(shí),我得到這樣的輸出:

Timing...
Enter
Exit
...done timing: 00:00:10.0149074

State Machine Fields

至此,我們已經(jīng)看到了生成的入口點(diǎn)方法,以及 MoveNext 實(shí)現(xiàn)中的一切是如何工作的。我們還了解了在狀態(tài)機(jī)上定義的一些字段。讓我們仔細(xì)看看這些。

對于前面給出的 CopyStreamToStream 方法:

public async Task CopyStreamToStreamAsync(Stream source, Stream destination)
{
    var buffer = new byte[0x1000];
    int numRead;
    while ((numRead = await source.ReadAsync(buffer, 0, buffer.Length)) != 0)
    {
        await destination.WriteAsync(buffer, 0, numRead);
    }
}

下面是我們最終得到的字段:

private struct <CopyStreamToStreamAsync>d__0 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncTaskMethodBuilder <>t__builder;
    public Stream source;
    public Stream destination;
    private byte[] <buffer>5__2;
    private TaskAwaiter <>u__1;
    private TaskAwaiter<int> <>u__2;
    ...
}

< > 1 __state。是“狀態(tài)機(jī)”中的“狀態(tài)”。它定義了狀態(tài)機(jī)所處的當(dāng)前狀態(tài),最重要的是下次調(diào)用 MoveNext 時(shí)應(yīng)該做什么。如果狀態(tài)為-2,則操作完成。如果狀態(tài)是-1,要么是我們第一次調(diào)用 MoveNext,要么是 MoveNext 代碼正在某個(gè)線程上運(yùn)行。如果你正在調(diào)試一個(gè) async 方法的處理過程,并且你看到狀態(tài)為-1,這意味著在某處有某個(gè)線程正在執(zhí)行包含在方法中的代碼。如果狀態(tài)大于等于0,方法會(huì)被掛起,狀態(tài)的值會(huì)告訴你在什么時(shí)候掛起。雖然這不是一個(gè)嚴(yán)格的規(guī)則(某些代碼模式可能會(huì)混淆編號(hào)),但通常情況下,分配的狀態(tài)對應(yīng)于從0開始的 await 編號(hào),按照源代碼從上到下的順序排列。例如,如果 async 方法的函數(shù)體完全是:

await A();
await B();
await C();
await D();

你發(fā)現(xiàn)狀態(tài)值是2,這幾乎肯定意味著 async 方法當(dāng)前被掛起,等待從 C() 返回的任務(wù)完成。

< > t__builder。這是狀態(tài)機(jī)的構(gòu)建器,例如用于 Task 的 AsyncTaskMethodBuilder,用于 ValueTask 的 AsyncValueTaskMethodBuilder<TResult>,用于 async void 方法的 AsyncVoidMethodBuilder,或用于 async 返回類型的 AsyncMethodBuilder(…)] 或通過 async 方法本身的屬性覆蓋的任何構(gòu)建器。如前所述,構(gòu)建器負(fù)責(zé) async 方法的生命周期,包括創(chuàng)建 return 任務(wù),最終完成該任務(wù),并充當(dāng)暫停的中介,async 方法中的代碼要求構(gòu)建器暫停,直到特定的 awaiter 完成。

編譯器完全按照參數(shù)名稱的指定來命名它們。如前所述,所有被方法主體使用的參數(shù)都需要被存儲(chǔ)到狀態(tài)機(jī)中,以便 MoveNext 方法能夠訪問它們。注意我說的是 "被使用"。如果編譯器發(fā)現(xiàn)一個(gè)參數(shù)沒有被異步方法的主體使用,它就可以優(yōu)化,不需要存儲(chǔ)這個(gè)字段。例如,給定下面的方法:

public async Task M(int someArgument)
{
    await Task.Yield();
}

編譯器會(huì)將這些字段發(fā)送到狀態(tài)機(jī):

private struct <M>d__0 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncTaskMethodBuilder <>t__builder;
    private YieldAwaitable.YieldAwaiter <>u__1;
    ...
}

請注意,這里明顯缺少名為 someArgument 的參數(shù)。但是,如果我們改變 async 方法,讓它以任何方式使用實(shí)參:

public async Task M(int someArgument)
{
    Console.WriteLine(someArgument);
    await Task.Yield();
}

它顯示:

private struct <M>d__0 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncTaskMethodBuilder <>t__builder;
    public int someArgument;
    private YieldAwaitable.YieldAwaiter <>u__1;
    ...
}

<buffer>5__2;。這是緩沖區(qū)的 "局部",它被提升為一個(gè)字段,這樣它就可以在等待點(diǎn)上存活。編譯器相當(dāng)努力地防止?fàn)顟B(tài)被不必要地提升。注意,在源碼中還有一個(gè)局部變量 numRead,在狀態(tài)機(jī)中沒有相應(yīng)的字段。為什么?因?yàn)樗鼪]有必要。這個(gè)局部變量被設(shè)置為 ReadAsync 調(diào)用的結(jié)果,然后被用作 WriteAsync 調(diào)用的輸入。在這兩者之間沒有 await,因此 numRead 的值需要被存儲(chǔ)。就像在一個(gè)同步方法中,JIT 編譯器可以選擇將這樣的值完全存儲(chǔ)在一個(gè)寄存器中,而不會(huì)真正將其溢出到堆棧中,C# 編譯器可以避免將這個(gè)局部變量提升為一個(gè)字段,因?yàn)樗恍枰谌魏蔚却斜4嫠闹怠R话銇碚f,如果 C# 編譯器能夠證明局部變量的值不需要在等待中保存,它就可以省略局部變量的提升。

<>u__1和<>u__2。async 方法中有兩個(gè) await:一個(gè)用于 ReadAsync 返回的 Task<int>,另一個(gè)用于 WriteAsync 返回的 Task。Task. getawaiter() 返回一個(gè) TaskAwaiter,Task<TResult>. getawaiter() 返回一個(gè) TaskAwaiter<TResult>,兩者都是不同的結(jié)構(gòu)體類型。由于編譯器需要在 await (IsCompleted, UnsafeOnCompleted) 之前獲取這些 awaiter,然后需要在 await (GetResult) 之后訪問它們,因此需要存儲(chǔ)這些 awaiter。由于它們是不同的結(jié)構(gòu)類型,編譯器需要維護(hù)兩個(gè)單獨(dú)的字段來做到這一點(diǎn)(另一種選擇是將它們裝箱,并為 awaiter 提供一個(gè)對象字段,但這會(huì)導(dǎo)致額外的分配成本)。不過,編譯器會(huì)盡可能地重復(fù)使用字段。如果我有:

public async Task M()
{
    await Task.FromResult(1);
    await Task.FromResult(true);
    await Task.FromResult(2);
    await Task.FromResult(false);
    await Task.FromResult(3);
}

有五個(gè)等待,但只涉及兩種不同類型的等待者:三個(gè)是 TaskAwaiter<int>,兩個(gè)是 TaskAwaiter<bool>。因此,狀態(tài)機(jī)上最終只有兩個(gè)等待者字段:

private struct <M>d__0 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncTaskMethodBuilder <>t__builder;
    private TaskAwaiter<int> <>u__1;
    private TaskAwaiter<bool> <>u__2;
    ...
}

然后,如果我將我的示例改為:

public async Task M()
{
    await Task.FromResult(1);
    await Task.FromResult(true);
    await Task.FromResult(2).ConfigureAwait(false);
    await Task.FromResult(false).ConfigureAwait(false);
    await Task.FromResult(3);
}

仍然只涉及 Task<int>s 和 Task<bool>s,但實(shí)際上我使用了四個(gè)不同的 struct awaiter 類型,因?yàn)閺?ConfigureAwait 返回的東西上的 GetAwaiter() 調(diào)用返回的 awaiter 與 Task.GetAwaiter() 返回的是不同的類型…從編譯器創(chuàng)建的 awaiter 字段可以再次很明顯的看出:

private struct <M>d__0 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncTaskMethodBuilder <>t__builder;
    private TaskAwaiter<int> <>u__1;
    private TaskAwaiter<bool> <>u__2;
    private ConfiguredTaskAwaitable<int>.ConfiguredTaskAwaiter <>u__3;
    private ConfiguredTaskAwaitable<bool>.ConfiguredTaskAwaiter <>u__4;
    ...
}

如果您發(fā)現(xiàn)自己想要優(yōu)化與異步狀態(tài)機(jī)相關(guān)的大小,您可以查看的一件事是是否可以合并正在等待的事情,從而合并這些 awaiter 字段。

您可能還會(huì)看到在狀態(tài)機(jī)上定義的其他類型的字段。值得注意的是,您可能會(huì)看到一些字段包含單詞“wrap”??紤]下面這個(gè)例子:

public async Task<int> M() => await Task.FromResult(42) + DateTime.Now.Second;

這將生成一個(gè)包含以下字段的狀態(tài)機(jī):

private struct <M>d__0 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncTaskMethodBuilder<int> <>t__builder;
    private TaskAwaiter<int> <>u__1;
    ...
}

到目前為止沒有什么特別的?,F(xiàn)在顛倒一下添加表達(dá)式的順序:

public async Task<int> M() => DateTime.Now.Second + await Task.FromResult(42);

這樣,你就得到了這些字段:

private struct <M>d__0 : IAsyncStateMachine
{
    public int <>1__state;
    public AsyncTaskMethodBuilder<int> <>t__builder;
    private int <>7__wrap1;
    private TaskAwaiter<int> <>u__1;
    ...
}

我們現(xiàn)在有了另一個(gè)函數(shù):<>7__wrap1。為什么?因?yàn)槲覀冇?jì)算了 DateTime.Now 的值。其次,只有在計(jì)算完它之后,我們才需要等待一些東西,并且需要保留第一個(gè)表達(dá)式的值,以便將其與第二個(gè)表達(dá)式的結(jié)果相加。因此,編譯器需要確保第一個(gè)表達(dá)式的臨時(shí)結(jié)果可以添加到 await 的結(jié)果中,這意味著它需要將表達(dá)式的結(jié)果溢出到臨時(shí)中,它使用 <>7__wrap1 字段做到了這一點(diǎn)。如果你發(fā)現(xiàn)自己對異步方法的實(shí)現(xiàn)進(jìn)行了超優(yōu)化,以減少分配的內(nèi)存量,你可以尋找這樣的字段,并查看對源代碼的微調(diào)是否可以避免溢出的需要,從而避免這種臨時(shí)的需要。

我希望這篇文章有助于解釋當(dāng)你使用 async/await 時(shí)背后到底發(fā)生了什么。這里有很多變化,所有這些結(jié)合在一起,創(chuàng)建了一個(gè)高效的解決方案,可以編寫可拓展的異步代碼,而不必處理回調(diào)。然而歸根結(jié)底,這些部分實(shí)際上是相對簡單的:任何異步操作的通用表示,一種能夠?qū)⑵胀刂屏髦貙憺閰f(xié)程的狀態(tài)機(jī)實(shí)現(xiàn)的語言和編譯器,以及將它們綁定在一起的模式。其他一切都是優(yōu)化的額外收獲。

以上就是 C# 語言中async/await工作原理SynchronizationContext 和 ConfigureAwait的詳細(xì)內(nèi)容,更多關(guān)于 C# 語言 async/await的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C#使用StopWatch獲取程序毫秒級(jí)執(zhí)行時(shí)間的方法

    C#使用StopWatch獲取程序毫秒級(jí)執(zhí)行時(shí)間的方法

    這篇文章主要介紹了C#使用StopWatch獲取程序毫秒級(jí)執(zhí)行時(shí)間的方法,涉及C#操作時(shí)間的相關(guān)技巧,需要的朋友可以參考下
    2015-04-04
  • Unity InputFiled TMP屬性和各種監(jiān)聽示例詳解

    Unity InputFiled TMP屬性和各種監(jiān)聽示例詳解

    這篇文章主要為大家介紹了Unity InputFiled TMP屬性和各種監(jiān)聽示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • 使用C#調(diào)用百度地圖并實(shí)現(xiàn)坐標(biāo)點(diǎn)的設(shè)置以及讀取示例

    使用C#調(diào)用百度地圖并實(shí)現(xiàn)坐標(biāo)點(diǎn)的設(shè)置以及讀取示例

    這篇文章主要介紹了使用C#調(diào)用百度地圖并實(shí)現(xiàn)坐標(biāo)點(diǎn)的設(shè)置以及讀取示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • c# 反射用法及效率對比

    c# 反射用法及效率對比

    這篇文章主要介紹了c# 反射用法及效率對比,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下
    2021-02-02
  • c# 用ffmpeg從視頻中截圖

    c# 用ffmpeg從視頻中截圖

    這篇文章主要介紹了c# 用ffmpeg從視頻中截圖的方法,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下
    2021-03-03
  • C#?as?和?is?運(yùn)算符區(qū)別和用法示例解析

    C#?as?和?is?運(yùn)算符區(qū)別和用法示例解析

    在C#中,as?和?is?關(guān)鍵字都用于處理類型轉(zhuǎn)換的運(yùn)算符,但它們有不同的用途和行為,本文我們將詳細(xì)解釋這兩個(gè)運(yùn)算符的區(qū)別和用法,需要的朋友可以參考下
    2025-01-01
  • Unity打包代碼到DLL的實(shí)現(xiàn)

    Unity打包代碼到DLL的實(shí)現(xiàn)

    本文主要介紹了Unity打包代碼到DLL的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • C#中的in參數(shù)與性能分析詳解

    C#中的in參數(shù)與性能分析詳解

    這篇文章主要給大家介紹了關(guān)于C#中in參數(shù)與性能分析的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • C#算法之實(shí)現(xiàn)阿姆斯特朗數(shù)

    C#算法之實(shí)現(xiàn)阿姆斯特朗數(shù)

    這篇文章介紹了C#實(shí)現(xiàn)阿姆斯特朗數(shù)的算法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-02-02
  • C#中執(zhí)行批處理文件(*.bat)的方法代碼

    C#中執(zhí)行批處理文件(*.bat)的方法代碼

    本文介紹一下在C#中執(zhí)行批處理文件(*.bat)的方法。
    2013-03-03

最新評論