C# Thread/Task
๐ซ lock
1
2
3
4
5
private readonly object thisLock = new object();
lock (thisLock)
{
// Bla Bla
}
์๊ณ ์์ญ (Critical Section
)์ ๋ง๋ค์ด์ฃผ๋ ํค์๋
Thread
๋ lock
์ ์ป์ด์ผ Critical Secion
์ ์์ฑํ ์ ์๋ค.
์ธ๋ถ ์ฝ๋์์๋ ์ ๊ทผํ ์ ์๋ this
, Type
ํ์ (typeof
, GetType()
), string
ํ์์ ๋งค๊ฐ๋ณ์๋ก ์ ๋ ์ฌ์ฉํ์ง ๋ง ๊ฒ.
@ Key, ์ ์ฉ ๊ฐ์ฒด ์ธ์คํด์ค
@ Critical Section
, ํ ๋ฒ์ ํ ์ค๋ ๋๋ง ์ ๊ทผํ ์ ์๋ ์ฝ๋ ์์ญ
๐ซ ์ค๋ ๋
๐ซง ์ค๋ ๋๋ ๊ฐ๋ฒผ์ด ์์์ธ๊ฐ?
์๋์ ์ผ๋ก ํ๋ก์ธ์ค๋ณด๋ค ๊ฐ๋ณ์ง๋ง,
์ ๋์ ์ผ๋ก ๋ฌด๊ฑฐ์ด ๋ฆฌ์์ค์ด๋ค.
- ๊ณต๊ฐ๋น์ฉ
Thread Kernel Object
- x86 : 700B
- x64 : 1240B
- ARM : 350B
Thread Environment Block
: 4KUser Mode stack
: 1MBKernel Mode stack
- 32bit OS : 12KB
- 64bit OS : 24KB
- ์ด : 1053KB ๋จ์ง
- ์๊ฐ๋น์ฉ
- DLL Thread attach/detach notification
- ํ๋ก์ธ์ค์ ์ค๋ ๋๋ฅผ ๋ง๋ค์ด์ง๋๋ง๋ค, ๊ฐ DLL์ main ํจ์๋ฅผ ํธ์ถ
- ๋ฌธ์์ธ์ฌ๋ฅผ ํ๋ ๊ฒ์ด์ง์, ์ค๋ ๋ ์๋ก ๋ง๋ค์ด์ก์ด์
- DLL์ ์ค๋ ๋๋ฅผ ์ํ ๊ณต๊ฐ์ ๋ง๋ จ
- ๋ํ์ ์ธ๊ฒ C Runtime Library (DLL)
Context Switching
- ์ด ์ค๋ ๋๊ฐ ๊ฐ๊ณ ์๋ ๊ฐ์์ CPU ์ ๋ณด๋ค์ ๋ก๋ํด์ ์ํํ๊ณ
- ์ผ์ ์๊ฐ (Quantum)์ด ์ง๋๋ฉด ๋ค์ ์ ์ฅํ๊ณ
- ๋ค์ ์ค๋ ๋๋ฅผ ๋ก๋โฆ
- -> DLL์ด ๋ง์ผ๋ฉด ๋ง์์๋ก, ์ค๋ ๋๊ฐ ๋ง์ผ๋ฉด ๋ง์์๋ก
- DLL Thread attach/detach notification
๐ซง ์ด์ ๊ทธ๋ง ํด์ผ ํ ๋ฐ๋ณด์ง
๐ซง ๋ช ์์ ์ผ๋ก ์ค๋ ๋๋ฅผ ์์ฑํ์ง ๋ง๋ผ
- ์์ธ
๋ณดํต
๋จ๊ณ์ ์ค๋ ๋ ์ฐ์ ์์๊ฐ ์๋ ์ค๋ ๋๊ฐ ํ์ํ ๊ฒฝ์ฐ- ํฌ๊ทธ๋ผ์ด๋ ์ค๋ ๋์ฒ๋ผ ๋์ํ๋ ์ค๋ ๋๊ฐ ํ์ํ ๊ฒฝ์ฐ
- ๊ณ์ฐ ์ค์ฌ์ ์์ ์ด ์๋นํ ์ค๋ซ๋์ ์ํ๋์ด์ผ ํ๋ ๊ฒฝ์ฐ
๊ฐ๋ฅํ Thread class๋ฅผ ์ด์ฉํ์ฌ ๋ช ์์ ์ผ๋ก ์ค๋ ๋๋ฅผ ์์ฑํ์ง ๋ง ๊ฒ
๐ซง ์ฌ๋ฌ ์ค๋ ๋๋ฅผ ์ฌ์ฉํ๋ ์ด์
- ์๋ต์ฑ์ ๊ฐ์
- ํด๋ผ์ด์ธํธ ์ธก UI ์ดํ๋ฆฌ์ผ์ด์
- UI ์ค๋ ๋
- ์์ ์ค๋ ๋์ ๊ฐ์๋ ๋์ด๋์ง๋ง, ์๋ต์ฑ์ด ๊ฐ์ ๋๋ฏ๋ก ์ ์ฒด์ ์ผ๋ก ์ข ๋ ๋์ ์์ฉ ํ๋ก๊ทธ๋จ์ผ๋ก ํ๋จ
- ์ฑ๋ฅ
- ํด๋ผ์ด์ธํธ, ์๋ฒ ์ธก Application
- ๋ค์ค CPUI์ ํํด์ ์ฑ๋ฅ ๊ฐ์
- ์ค๋ ๋๋ฅผ ๊ฐ์ฅ ์ ํ์ฉํ๋ ๋ฐฉ๋ฒ
- ์ค๋ ๋ ํ์ ์ด์ฉํ๊ณ ๋น๋๊ธฐ๋ก ์์ ์ ์ํํ๋ผ
- -> ์ฐ๋ฆฌ๊ฐ TASK ๋ณ๋ ฌํ๋ฅผ ์์์ผ ํ๋ ์ด์
๐ซง ๊ณ์ฐ ์ค์ฌ ๋น๋๊ธฐ ์์
- CLR ์ค๋ ๋ ํ
- ์ฌ์ค ์ด๋ฏธ ๋ง๋ค์ด์ ธ ์์
- ๋ช ์์ ์ผ๋ก ์ฐ์ง์๊ณ ์์์ ๋ฟ
- Requests Queue์ ์์ ์ด ๋ค์ด์ค๋ฉด, ๊ทธ๋ ์ค๋ ๋๋ฅผ ํ ๋น
- ์ต๋ํ ํ๋๋ง ์ฐ๋ ค๊ณ ํจ
- ๋ง์ด ๋ค์ด์ค๋ฉด, ์ฝ์ด ์๋งํผ๋ง ์ค๋ ๋๋ฅผ ๋ง๋ค์ด์ ์ฌ์ฉ
- Request Queue์ ์์
์ด ์ผ์ ์๊ฐ ๋ค์ด์ค์ง ์์ผ๋ฉด ์ค๋ ๋๋ฅผ ์ ๊ฑฐ
- ์์ฑ/์ ๊ฑฐ ๋น์ฉ ์ต์ํ
๐ซง QueueUserWorkItem
1
public static bool QueueUserWorkItem(WaitCallback callBack, object state);
1
2
3
4
5
6
7
8
9
for (int i = 0; i < 100; i++)
{
ThreadPool.QueueUserWorkItem((obj) =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
});
}
Console.ReadLine();
- ์์ ์๋ฃ ์์ ์ ์ ์ ์์
- ์์ ์ํ ๊ฒฐ๊ณผ๋ฅผ ์ป์ด ์ฌ ์ ์์
- ์ทจ์ / ์์ธ ์ฒ๋ฆฌ ๋ถ๊ฐ๋ฅ
- -> ์ฐ๊ธฐ ์ฝ์ง๋ง ์ด๋ฐ ํ๊ณ๋ค ๋๋ฌธ์ ์ ์์
๐ซง Task
Task ๋๋๋ฉด ์ฅ์
Taks ๊ฐ์ ์๊ด๊ด๊ณ๊ฐ ์๋ค๋ฉด, ๋ณ๋ ฌ๋ก ์ํ ๊ฐ๋ฅ -> ๋นจ๋ผ์ง๋ค
1
2
3
4
5
6
7
8
9
10
// QueueUserWorkItem๊ณผ ์ ์ฌ ๋์์ ์ํํ๋ ์ฝ๋ ํจํด
Action action = () =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
};
Task task = new Task(action); // #1: Task ๊ฐ์ฒด ์์ฑ ํ
task.Start(); // Start ๋ช
์์ ํธ์ถ
Task.Run(action); // #2: Task.Run์ ์ด์ฉํ์ฌ ์์
์ํ
1
2
3
4
5
6
// ๊ฒฐ๊ณผ ๊ฐ์ ๊ฐ์ ธ์ค๋ Task ๊ฐ์ฒด ์์ฑ, Sum ํธ์ถ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค๋ฉด?
Task<int> task = new Task<int>((n) => Sum((int)n), 100);
t.Start(); // ๋ช
์์ ์ํ
t.Wait(); // Task ์๋ฃ ๋๊ธฐ (์๋ฃ ์์ ์ ์ ์ ์๋ค)
Console.WrtieLine(t.Result); // t.Result ๊ฒฐ๊ณผ ํ๋ (๊ฒฐ๊ณผ๋ฅผ ๋ฐ์์ฌ ์ ์๋ค)
Canceling a Task
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private static int Sum(CancellationToken ct, int n)
{
int sum = 0;
for (; n > 0; n--)
{
// ์์
์ทจ์๊ฐ ์์ฒญ๋๋ฉด OperationCanceledException์
// innerException์ผ๋ก ๊ฐ๋ AggregateException์ ๋์ง
ct.ThrowIfCancellationRequested();
checked
{
sum += n;
}
}
return sum;
}
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
Task<Int32> t = Task.Run(() => Sum(cts.Token, 100000000), cts.Token);
cts.Cancel(); // ์์
์ทจ์ ์์ฒญ
try
{
Console.WriteLine("The result is: " + t.Result);
}
catch (AggregateException e)
{
e.Handle((innerException) => innerException is OperationCanceledException);
// Operation.. ์ด๋ฉด ์ฒ๋ฆฌ๋ ๊ฒ์ผ๋ก
Console.WriteLine("Exception: " + e.InnerExceptions[0].Message);
}
}
- ์์ ์๋ฃ ์์ ์ ์ ์ ์์
- ์์ ์ํ ๊ฒฐ๊ณผ๋ฅผ ์ป์ด ์ฌ ์ ์์
- ์ทจ์ / ์์ธ ์ฒ๋ฆฌ ๊ฐ๋ฅ
๐ซง Task ์ฐ๊ฒฐ1
1
2
3
4
5
6
// ์จ์ดํ
์ํคํ
์ฒ
Task<int> task = new Task<int>((n) => Sum((int)n), 100);
t.Start();
t.Wait(); // ๋๊ธฐ
Console.WrtieLine(t.Result);
to
1
2
3
4
5
6
7
8
9
10
11
// waitfree, lockfree ์ํคํ
์ฒ (์๋ฒ์์ ๋ง์ด ์ฌ์ฉ)
// t Task๊ฐ ์๋ฃ๋๋ฉด cwt Task๋ฅผ ์ํ
Task<Int32> t = Task.Run(() => Sum(CancellationTokenSource.None, 100));
Task cwt = t.ContinueWith( // ์๋ฃ๋๋ฉด
(antecedent) =>
{
Console.WriteLine("The result is: " + antecedent.Result);
});
// ์ฐ๊ฒฐํ๊ณ ๋ฐ๋ก ๋น ์ ธ๋์ด
๐ซง Task ์ฐ๊ฒฐ2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// TaskContinuationOptions
// OnlyOnCanceled, OnlyOnFaulted, OnlyOnRanToCompletion ๊ทธ์ธ ๊ธฐํ ๋ฑ๋ฑ
CancellationTokeSource cts = new CancellationTokenSource();
cts.Cancel();
Task<Int32> t = Task.Run(() => Sum(cts.Token, 100000000), cts.Token);
t.ContinueWith((task) => // ์ฑ๊ณต ์๋ฃ์
{
Console.WriteLine("The result is: " + task.Result);
}, TaskContinuationOptions.OnlyOnRanToCompletion);
t.ContinueWith((task) => // ์คํจ/์์ธ ๋ฐ์ ์
{
Console.WriteLine("The task failed" + task.Exception.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);
t.ContinueWith((task) => // ์ทจ์์
{
Console.WriteLine("The task was canceled");
}, TaskContinuationOptions.OnlyOnCanceled);
๐ซง Task ์ฐ๊ฒฐ3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Parent-Child Task๋ก์ ์ฐ๊ฒฐ, TaskCreationOptions.AttachedToParent
Task<Int32[]> parent = new Task<Int32[]>(() =>
{
var results = new Int32[3];
new Task(() => results[0] = Sum(CancellationToken.None, 10000), TaskCreationOptions.AttachedToParent).Start();
new Task(() => results[1] = Sum(CancellationToken.None, 20000), TaskCreationOptions.AttachedToParent).Start();
new Task(() => results[2] = Sum(CancellationToken.None, 30000), TaskCreationOptions.AttachedToParent).Start();
return results;
});
// Child Task๋ค์ด ๋ชจ๋ ์๋ฃ๋๋ฉด = parent๊ฐ ์๋ฃ๋๋ฉด
var cwt = parent.ContinueWith((parentTask) => // parentTask๊ฐ ๋๋๋ฉด ์ํํ Task ์ฐ๊ฒฐ
{
Array.ForEach(parentTask.Result, Console.WriteLine);
});
parent.Start();
๐ซง I/O ์ค์ฌ์ ๋น๋๊ธฐ ์์
๐ซง ๋๊ธฐ I/O ๋งค์ปค๋์ฆ
๐ซง ๋น๋๊ธฐ I/O ๋งค์ปค๋์ฆ
- ๋น๋๊ธฐ
- ์์ ์ ํ๋ ์ฃผ์ฒด์ ์์ ์ ์์ฒญํ๋ ์ฃผ์ฒด๊ฐ ๋ค๋ฆ
- ์์
์ ํ๋ ์ฃผ์ฒด๊ฐ ์์
์ ์์ฒญํ๋ ์ฃผ์ฒด์๊ฒ ์๋ ค์ฃผ๋ ๋ฐฉ์
- (H/W๊ฐ ์๋ฃํ๋ฉด ์ค๋ ๋์๊ฒ ๋ ธํฐํผ์ผ์ด์ ์ ์ค)
์ด๋ฅผ ์ด๋ป๊ฒ ํจํดํํ๋๋?
๐ซง Comparing Patterns
- Sync
1
2
public int Read(byte[] buffer, int offset, int count);
// ๊ทผ๋ฐ ๋น๋๊ธฐ๊ฐ ์ข์์?
- APM (Asynchronous Programming Model)
1
2
3
4
5
6
public IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state);
public int EndRead(IAsyncResult asyncResult);
// ๋น๋๊ธฐ : ์ํค๋ ๋ฐฉ์๊ณผ ๊ฒฐ๊ณผ๋ฅผ ์ทจํ๋ ๋ฐฉ์์ด ๋ค๋ฆ
// ๋ฌธ์ : ๋งค๊ฐ๋ณ์๊ฐ ๋ง์์ง, ๋ฐ์ ๋๋ ๋ณต์กํ๊ณ , EndRead๋ฅผ ์ธ์ ํธ์ถํด์ผ ํ ์ง ์ ๋งคํจ
// ์ข ๋ ์ฌ์ด ๋ฐฉ๋ฒ?
- EAP (Event-based Asynchronous Pattern)
1
2
3
4
5
6
public void ReadAsync(byte[] buffer, int offset, int count);
public event ReadCompletedEventHandler ReadCompleted;
// ๊ฒฐ๊ณผ๋ฅผ ์ทจํ๋ ๋ฐฉ์์ ์ด๋ฒคํธ๋ก
// ๋ฌธ์ : APM, EAP ๋ ๋ค ์์
์ ์ํค๋ ์์น์ ๋ฐ๋ ์์น๊ฐ ๋ค๋ฆ
// (ํธ์ถํ๋ ์ชฝ๊ณผ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ ์ฒ๋ฆฌํ๋ ํจ์(์ฝ๋ฐฑ) ์ฌ์ด์ ์ปจํ
์คํธ๋ฅผ ๋๊ธฐ๊ธฐ ์ํด ์ง์ญ๋ณ์๋ ๋งค๊ฐ๋ณ์๋ฅผ ๋ง๋ค์ด ๋๊ฒจ์ผ ํจ)
- TAP (Task-based Asynchronous Pattern)
1
public Task<int> ReadAsync(byte[] buffer, int offset, int count);
Sync, TAP ๊ฐ์ ๋ฉ์๋ ์ํ์ด ๊ฐ์ฅ ์ ์ฌํจ.
-> ๊ฐ์ฅ ์ง๊ด์ ์ด๊ณ , Sync ๋ฐฉ์๊ณผ ๋ฎ์ ์์ด ์ฌ์ฉํ๊ธฐ ์ฌ์ด ๋น๋๊ธฐ ํจํด
๐ซง Async/Await
๋ด๋ถ ๋์
async
๋ฉ์๋๋ Task
๋ฅผ ๋ฐํ
await
ํค์๋๋ Task
๋ฅผ ๋ฐ์์ Task
๊ฐ ์๋ฃ๋ ๋๊น์ง ๋๊ธฐ
await
ํค์๋๋ Task
๊ฐ ์๋ฃ๋๋ฉด Task
์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ์ค์ ์กฐ์ฌ !!
// ์ด๊ฑด ํจ์๊ฐ ์ฒด์ธ๋์ด ์์ด์, ์ฒซ๋ฒ์งธ ํจ์๊ฐ ๋๋์ผ ๋๋ฒ์งธ ํจ์๊ฐ ์คํ๋จ
await SomeMethodAsync();
await SomeMethodAsync();
// ConfigureAwait(false)๋ฅผ ์ฐ๋ฉด ์ฒซ๋ฒ์งธ ํจ์๊ฐ ๋๋๊ธฐ ์ ์ ๋๋ฒ์งธ ํจ์๊ฐ ์คํ๋จ
await SomeMethodAsync().ConfigureAwait(false);
await SomeMethodAsync().ConfigureAwait(false);
// ์๋๋ฉด Task๋ฅผ ๋จผ์ ๋ฐ์์, ๊ทธ๊ฑธ await
Task t = SomeMethodAsync();
Task t2 = SomeMethodAsync();
await t;
await t2;
// WhenAll์ ์ธ ์๋ ์์
await Task.WhenAll(t, t2);
// WhenAny๋ฅผ ์ธ ์๋ ์์
var tasks = new List<Task>() { t, t2 };
while (tasks.Count > 0)
{
Task t = await Task.WhenAny(tasks);
tasks.Remove(t);
}
๐ซ CancellationTokenSource, CancellationToken
๋น๋๊ธฐ ์์ ์ ์ทจ์ํ๋ ์ฉ๋
CancellationTokenSource
ํด๋์ค (cts)CancellationToken
์ ์์ฑํ๊ณ , Cancel ์์ฒญ์CancellationToken
์๊ฒ ๋ณด๋ด๋ ์ญํCancel()
: ๋ฐํํ ๋ชจ๋ ํ ํฐ์ ์ทจ์ ์ ํธCancelAfter(TimeSpan delay)
: โIsCancellationRequested
: ์ทจ์๊ฐ ์์ฒญ๋์๋์ง ์ฌ๋ถ
CancellationToken
๊ตฌ์กฐ์ฒด- ํ์ฌ Cancel ์ํ๋ฅผ ๋ชจ๋ํฐ๋ง ํ๋ ๊ตฌ์กฐ์ฒด
- ์ฌ๋ฌ Listener๋ค์ ์ํด ์ฌ์ฉ๋จ
Register(Action callback)
: ์ทจ์๊ฐ ์์ฒญ๋์์ ๋ ํธ์ถ๋ ์ฝ๋ฐฑ์ ๋ฑ๋กIsCancellationRequested
: ์ทจ์๊ฐ ์์ฒญ๋์๋์ง ์ฌ๋ถ
๐ซง ์ฌ์ฉ
CancellationTokenSource
ํ๋ ์ ์ธCancellationTokenSource
๊ฐ์ฒด ์์ฑ- ๋น๋๊ธฐ ์์
๋ฉ์๋ ์์์ ์์
์ด ์ทจ์๋์๋์ง๋ฅผ ์ฒดํฌํ๋ ์ฝ๋
if (cancelTokenSource.Token.IsCancellationRequested) => return null;
- ์ทจ์ ์์ฒญ
cancelTokenSource.Cancel();
cts.Dispose()
using
๋ธ๋ก์ ์ผ๋ค๋ฉด, ๋ธ๋ก์ ๋ฒ์ด๋ ๋ ์๋์ผ๋ก ํธ์ถ- ๊ทธ๊ฒ ์๋๋ผ๋ฉด ์์๋ก ํธ์ถ
๐ซ Ref
์ฐธ๊ณ : โC#์ ์ด์ฉํ Task ๋ณ๋ ฌํ์ ๋น๋๊ธฐ ํจํดโ
์ฐธ๊ณ : โC# ๋น๋๊ธฐ ์ฌ์ฉ ์์ (Task, WhenAll, WhenAny)โ
์ฐธ๊ณ : โC# ๋น๋๊ธฐ/๋๊ธฐ/์์
์ค๋ช
(์ฌ์ธต ๋ถ์)โ
์ฐธ๊ณ : โC# - CancellationTokenโ
๋ฐ ๋จ๊ณ๋ ๋๊ฐ์ง๋ง
์กฐ๊ธ ์ถ์ํํด๋ณด๋ฉด
์ค๋ ๋๋ ํฌ๊ทธ๋ผ์ด๋ ์ค๋ ๋์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋๋ก ๋๋จ
ํฌ๊ทธ๋ผ์ด๋ ์ค๋ ๋๊ฐ ์ข
๋ฃ๋์ง ์์ผ๋ฉด ํ๋ก์ธ์ค๊ฐ ์ข
๋ฃ๋์ง ์์
๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋๋ ํ๋ก์ธ์ค๊ฐ ์ข
๋ฃ๋๋ฉด ์ข
๋ฃ๋จ
Delegate : ๋ด๋ถ์ ์ผ๋ก ์ค๋ธ์ ํ ์ ํฉ๋๋ค๋ง, ์ค๋ธ์ ํธ์์ ์ฝ๋ฐฑ ํจ์์ ๊ดํ ํฌ์ธํฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ ํ์
๋นํธ๋ ๋ฒจ parallel
๋ฐ์ดํฐ parallel
task parallel
Task.Delay(1000); Task.Run(() => { });
async ํค์๋๋ฅผ ์ฐ๋ฉด
ํจ์๊ฐ ์ํ๋จธ์ ์ผ๋ก ๋ณํ๋จ
์ํ๋จธ์ ์ ํจ์๊ฐ ์คํ๋๋ ๋์ ์ํ๋ฅผ ์ ์ฅํ๊ณ , ๋ค์ ์คํ๋ ๋ ์ํ๋ฅผ ๋ณต์ํ๋ ๊ฒ
๐ซ ๋๊ธฐ/๋น๋๊ธฐ
๐ซง ๋๊ธฐ
Synchrounous
๋ฉ์๋๋ฅผ ํธ์ถํ ์ดํ, ๋ฉ์๋๊ฐ ์ข
๋ฃ๋ ๋๊น์ง ์ฝ๋ ์คํ์ด ์ฐจ๋จ๋จ
์ฆ, ์์
์ ์์๋๋ก ์คํ๋๋ฏ๋ก ํ๋์ ์์
์ด ๋๋์ผ ๋ค์ ์์
์ ์ํํ ์ ์์
๐ซง ๋น๋๊ธฐ
Asynchronous
๋ฉ์๋๋ฅผ ํธ์ถํ ์ดํ ํด๋น ๋ฉ์๋๊ฐ ์ข
๋ฃ๋์ง ์์๋ ์ฝ๋ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๋ค์ ์ฝ๋ ์คํ
๋น๋๊ธฐ ๋ฉ์๋๋ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋์์ ์ํ๋๋ฏ๋ก, ๋ฉ์ธ์ค๋ ๋๋ ๋ค๋ฅธ ์์
์ ์ํํ ์ ์์