C# Language
비동기 / 대기, 백그라운드 작업자, 작업 및 스레드 예제
수색…
비고
이러한 예제를 실행하려면 다음과 같이 호출하십시오.
static void Main()
{
new Program().ProcessDataAsync();
Console.ReadLine();
}
ASP.NET 구성 대기 중
ASP.NET이 요청을 처리하면 스레드 풀에서 스레드가 할당되고 요청 컨텍스트 가 만들어집니다. 요청 컨텍스트에는 정적 HttpContext.Current
속성을 통해 액세스 할 수있는 현재 요청에 대한 정보가 들어 있습니다. 그런 다음 요청에 대한 요청 컨텍스트가 요청을 처리하는 스레드에 할당됩니다.
주어진 요청 컨텍스트 는 한 번에 하나의 스레드에서만 활성화 될 수 있습니다 .
실행이 await
지면 비동기 메서드가 실행되는 동안 요청을 처리하는 스레드가 스레드 풀로 반환되고 다른 스레드가 사용할 수 있도록 요청 컨텍스트가 해제됩니다.
public async Task<ActionResult> Index()
{
// Execution on the initially assigned thread
var products = await dbContext.Products.ToListAsync();
// Execution resumes on a "random" thread from the pool
// Execution continues using the original request context.
return View(products);
}
작업이 완료되면 스레드 풀이 다른 스레드를 할당하여 요청 실행을 계속합니다. 그런 다음 요청 문맥이이 스레드에 할당됩니다. 이것은 원래 스레드 일 수도 있고 아닐 수도 있습니다.
블로킹
async
메서드 호출의 결과가 동 기적 으로 대기 할 때 교착 상태가 발생할 수 있습니다. 예를 들어 다음 코드는 IndexSync()
가 호출 될 때 교착 상태가 발생합니다.
public async Task<ActionResult> Index()
{
// Execution on the initially assigned thread
List<Product> products = await dbContext.Products.ToListAsync();
// Execution resumes on a "random" thread from the pool
return View(products);
}
public ActionResult IndexSync()
{
Task<ActionResult> task = Index();
// Block waiting for the result synchronously
ActionResult result = Task.Result;
return result;
}
이는 기본적으로 db.Products.ToListAsync()
작업 인 db.Products.ToListAsync()
가 컨텍스트 (ASP.NET의 경우 요청 컨텍스트)를 캡처하고 완료되면 사용하려고하기 때문입니다.
전체 호출 스택이 비동기 일 때 문제가되지 않습니다. 원래 await
에 도달하면 원래 스레드가 릴리스되어 요청 컨텍스트가 해제되기 때문입니다.
Task.Result
또는 Task.Wait()
(또는 다른 차단 메서드)를 사용하여 동기식으로 차단하면 원래 스레드가 여전히 활성화되어 요청 컨텍스트를 유지합니다. 대기중인 메소드는 여전히 비동기 적으로 작동하고 콜백이 실행을 시도하면, 즉 대기중인 작업이 반환되면 요청 문맥을 얻으려고 시도합니다.
따라서 요청 문맥을 가진 블로킹 스레드가 비동기 연산이 완료되기를 기다리는 동안 비동기 연산은 완료하기 위해 요청 문맥을 얻으려고하기 때문에 교착 상태가 발생합니다.
ConfigureAwait
기본적으로 대기중인 작업에 대한 호출은 현재 컨텍스트를 캡처하고 완료되면 컨텍스트에서 실행을 다시 시도합니다.
ConfigureAwait(false)
하면이 문제를 방지하고 교착 상태를 피할 수 있습니다.
public async Task<ActionResult> Index()
{
// Execution on the initially assigned thread
List<Product> products = await dbContext.Products.ToListAsync().ConfigureAwait(false);
// Execution resumes on a "random" thread from the pool without the original request context
return View(products);
}
public ActionResult IndexSync()
{
Task<ActionResult> task = Index();
// Block waiting for the result synchronously
ActionResult result = Task.Result;
return result;
}
이렇게하면 비동기 코드를 차단해야 할 때 교착 상태를 피할 수 있습니다. 단, 이는 연속에서 컨텍스트를 잃어 버리는 대가가됩니다 (호출 대기 후 코드).
ASP.NET에서 이것은 호출을 따르는 코드가 await someTask.ConfigureAwait(false);
를 await someTask.ConfigureAwait(false);
경우이를 의미합니다 await someTask.ConfigureAwait(false);
컨텍스트에서 정보에 액세스하려고 시도합니다 (예 : HttpContext.Current.User
. 정보가 손실되었습니다. 이 경우 HttpContext.Current
는 null입니다. 예 :
public async Task<ActionResult> Index()
{
// Contains information about the user sending the request
var user = System.Web.HttpContext.Current.User;
using (var client = new HttpClient())
{
await client.GetAsync("http://google.com").ConfigureAwait(false);
}
// Null Reference Exception, Current is null
var user2 = System.Web.HttpContext.Current.User;
return View();
}
ConfigureAwait(true)
를 사용하면 (ConfigureAwait가 전혀없는 것과 같습니다) user
와 user2
가 동일한 데이터로 채워집니다.
이런 이유로 컨텍스트가 더 이상 사용되지 않는 라이브러리 코드에서 ConfigureAwait(false)
를 사용하는 것이 좋습니다.
비동기 / 대기
async / await를 사용하여 백그라운드 프로세스에서 시간 집약적 인 작업을 수행하는 방법에 대한 간단한 예제를 보시고 완료하기 위해 시간이 많이 소요되는 것을 기다릴 필요가없는 몇 가지 다른 옵션을 유지하는 옵션을 유지하십시오.
그러나 나중에 시간 집약적 인 방법으로 결과를 처리해야하는 경우 실행을 기다려이 작업을 수행 할 수 있습니다.
public async Task ProcessDataAsync()
{
// Start the time intensive method
Task<int> task = TimeintensiveMethod(@"PATH_TO_SOME_FILE");
// Control returns here before TimeintensiveMethod returns
Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
// Wait for TimeintensiveMethod to complete and get its result
int x = await task;
Console.WriteLine("Count: " + x);
}
private async Task<int> TimeintensiveMethod(object file)
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(file.ToString()))
{
string s = await reader.ReadToEndAsync();
for (int i = 0; i < 10000; i++)
s.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
// return something as a "result"
return new Random().Next(100);
}
BackgroundWorker
BackgroundWorker
객체를 사용하여 백그라운드 스레드에서 시간 집약적 인 작업을 수행하는 방법에 대한 간단한 예제는 아래를 참조하십시오.
당신은 다음을해야합니다 :
- 시간이 많이 소요되는 작업을 수행하는 작업자 메서드를 정의하고
BackgroundWorker
의DoWork
이벤트에 대한 이벤트 처리기에서 호출합니다. -
RunWorkerAsync
실행을 시작하십시오.DoWork
첨부 된 작업자 메소드에 필요한 모든 인수는DoWorkEventArgs
매개 변수를 통해RunWorkerAsync
로 전달 될 수 있습니다.
DoWork
이벤트 외에도 BackgroundWorker
클래스는 사용자 인터페이스와 상호 작용하는 데 사용되는 두 가지 이벤트를 정의합니다. 이들은 선택 사항입니다.
-
DoWork
핸들러가 완료되면RunWorkerCompleted
이벤트가 트리거됩니다. -
ProgressChanged
이벤트는ReportProgress
메서드가 호출 될 때 트리거됩니다.
public void ProcessDataAsync()
{
// Start the time intensive method
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += BwDoWork;
bw.RunWorkerCompleted += BwRunWorkerCompleted;
bw.RunWorkerAsync(@"PATH_TO_SOME_FILE");
// Control returns here before TimeintensiveMethod returns
Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
}
// Method that will be called after BwDoWork exits
private void BwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// we can access possible return values of our Method via the Parameter e
Console.WriteLine("Count: " + e.Result);
}
// execution of our time intensive Method
private void BwDoWork(object sender, DoWorkEventArgs e)
{
e.Result = TimeintensiveMethod(e.Argument);
}
private int TimeintensiveMethod(object file)
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(file.ToString()))
{
string s = reader.ReadToEnd();
for (int i = 0; i < 10000; i++)
s.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
// return something as a "result"
return new Random().Next(100);
}
태스크
Task
을 사용하여 백그라운드 프로세스에서 시간 집약적 인 Task
을 수행하는 방법에 대한 간단한 예는 아래를 참조하십시오.
Task.Run()
호출에서 시간 집중적 인 메서드를 래핑하면됩니다.
public void ProcessDataAsync()
{
// Start the time intensive method
Task<int> t = Task.Run(() => TimeintensiveMethod(@"PATH_TO_SOME_FILE"));
// Control returns here before TimeintensiveMethod returns
Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
Console.WriteLine("Count: " + t.Result);
}
private int TimeintensiveMethod(object file)
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(file.ToString()))
{
string s = reader.ReadToEnd();
for (int i = 0; i < 10000; i++)
s.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
// return something as a "result"
return new Random().Next(100);
}
실
Thread
를 사용하여 백그라운드 프로세스에서 시간 집약적 인 작업을 수행하는 방법에 대한 간단한 예는 아래를 참조하십시오.
public async void ProcessDataAsync()
{
// Start the time intensive method
Thread t = new Thread(TimeintensiveMethod);
// Control returns here before TimeintensiveMethod returns
Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
}
private void TimeintensiveMethod()
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(@"PATH_TO_SOME_FILE"))
{
string v = reader.ReadToEnd();
for (int i = 0; i < 10000; i++)
v.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
}
보시다시피 Thread
는 void 메소드를 매개 변수로 기대하기 때문에 TimeIntensiveMethod
에서 값을 반환 할 수 없습니다.
Thread
에서 리턴 값을 얻으려면 이벤트 나 다음 중 하나를 사용하십시오.
int ret;
Thread t= new Thread(() =>
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(file))
{
string s = reader.ReadToEnd();
for (int i = 0; i < 10000; i++)
s.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
// return something to demonstrate the coolness of await-async
ret = new Random().Next(100);
});
t.Start();
t.Join(1000);
Console.Writeline("Count: " + ret);
작업 "실행 및 잊어"확장
특정 경우 (예 : 로깅) 작업을 실행하는 것이 유용 할 수 있으며 결과를 기다리지 마십시오. 다음 확장을 사용하면 작업을 실행하고 나머지 코드를 계속 실행할 수 있습니다.
public static class TaskExtensions
{
public static async void RunAndForget(
this Task task, Action<Exception> onException = null)
{
try
{
await task;
}
catch (Exception ex)
{
onException?.Invoke(ex);
}
}
}
결과는 확장 메소드 내에서만 기다린다. async
/ await
가 사용되기 때문에 예외를 포착하고이를 처리하기위한 선택적 메소드를 호출 할 수 있습니다.
확장 프로그램 사용 방법의 예 :
var task = Task.FromResult(0); // Or any other task from e.g. external lib.
task.RunAndForget(
e =>
{
// Something went wrong, handle it.
});