async修饰符,await运算符关键字(C# 参考)

使用 async 修饰符可将方法、lambda 表达式或匿名方法指定为异步。 如果对方法或表达式使用此修饰符,则其称为异步方法。

public async Task<int> ExampleMethodAsync()  
{  
    // . . . .  
}

如果你不熟悉异步编程或不了解异步方法如何使用 await 关键字来完成可能需要长时间运行的工作而不阻止调用方的线程,请阅读使用 async 和 await 的异步编程中的简介。

string contents = await contentsTask;

方法将同步运行,直至到达其第一个 await 表达式,此时会将方法挂起,直到等待的任务完成。 同时,如下节示例中所示,控件将返回到方法的调用方。

如果 async 关键字修改的方法不包含 await 表达式或语句,则该方法将同步执行。 编译器警告将通知你不包含 await 的任何异步方法,因为该情况可能表示存在错误。 请参阅编译器警告(等级 1)CS4014。

async 关键字是上下文关键字,原因在于只有当它修饰方法、lambda 表达式或匿名方法时,它才是关键字。 在所有其他上下文中,都会将其解释为标识符。

示例

下面的示例展示了异步事件处理程序 StartButton_Click 和异步方法 ExampleMethodAsync 之间的控制结构和流程。 此异步方法得到的结果是一个下载网站的长度。 此代码适用于在 Visual Studio 中创建的 Windows Presentation Foundation (WPF) 应用或 Windows 应用商店应用;请参见有关设置应用的代码注释。

private async void StartButton_Click(object sender, RoutedEventArgs e)  
{  

    ResultsTextBox.Text += "\n";  
    try  
    {  
        int length = await ExampleMethodAsync();  

        ResultsTextBox.Text += String.Format("Length: {0}\n", length);  
    }  
    catch (Exception)  
    {  

    }  
}  

public async Task<int> ExampleMethodAsync()  
{  
    var httpClient = new HttpClient();  
    int exampleInt = (await httpClient.GetStringAsync("http://msdn.microsoft.com")).Length;  
    ResultsTextBox.Text += "Preparing to finish ExampleMethodAsync.\n";  
    return exampleInt;  
}  
// Output:  
// Preparing to finish ExampleMethodAsync.  
// Length: 53292  

返回类型

异步方法的返回类型可为 TaskTask<TResult>void。 方法不能声明任何 ref 或 out 参数,但是可以调用具有这类参数的方法。

如果异步方法的 语句指定一个 类型的操作数,则应指定 Task<TResult> 作为方法的返回类型TResult。 如果当方法完成时未返回有意义的值,则应使用 Task。 即,对方法的调用将返回一个 Task,但是当 Task 完成时,任何等待 await 的所有 Task 表达式的计算结果都为 void。

你应主要使用 void 返回类型来定义事件处理程序,这些处理程序需要此返回类型。 void 返回异步方法的调用方不能等待,并且无法捕获该方法引发的异常。


await 运算符在异步方法应用于任务,以挂起执行方法,直到所等待的任务完成。 任务表示正在进行的工作。

在其中使用 await 的异步方法必须通过 async 关键字进行修改。 使用 async 修饰符定义并且通常包含一个或多个 await 表达式的这类方法称为异步方法。

应用 await 运算符的任务通常是实现基于任务的异步模式的方法调用的返回值。 示例包括类型 TaskTask<TResult> 的值。

在下列代码中,HttpClient 方法GetByteArrayAsync 返回 Task\<byte[]>getContentsTask。 当任务完成时,任务约定生成实际字节数组。 await 运算符应用于 getContentsTask 以在 SumPageSizesAsync 中挂起执行,直到 getContentsTask 完成。 同时,控制权会返回给 SumPageSizesAsync 的调用方。 当 getContentsTask 完成之后,await 表达式计算为字节数组。

private async Task SumPageSizesAsync()  
{  
    //要在桌面应用程序中使用HttpClient类型,您必须包含using指令,并为System.Net.Http命名空间添加引用。
    HttpClient client = new HttpClient();  
    // . . .  
    Task<byte[]> getContentsTask = client.GetByteArrayAsync(url);  
    byte[] urlContents = await getContentsTask;  

    //byte[] urlContents = await client.GetByteArrayAsync(url);  
    // . . .  
}

如前面的示例中所示,如果 await 应用于返回 Task<TResult> 的方法调用结果,则 await 表达式的类型为 TResult。 如果 await 应用于返回 Task 的方法调用结果,则 await 表达式的类型为 void。 以下示例演示了差异。

// 关键字await用于 返回任务<TResult>的方法
TResult result = await AsyncMethodThatReturnsTaskTResult();  

//关键字await与返回任务的方法一起使用 
await AsyncMethodThatReturnsTask();

await 表达式不阻止正在执行它的线程。 而是使编译器将剩下的异步方法注册为等待任务的延续任务。 控制权随后会返回给异步方法的调用方。 任务完成时,它会调用其延续任务,异步方法的执行会在暂停的位置处恢复。

await 表达式只能在由 async 修饰符标记的立即封闭方法体、lambda 表达式或异步方法中出现。 术语 await 在该上下文中仅用作关键字。 在其他位置,它会解释为标识符。 在方法、Lambda 表达式或匿名方法中,await 表达式不能在同步函数体、查询表达式、lock 语句块或不安全上下文中出现

异常

大多数异步方法将返回 TaskTask<TResult>。 返回任务的属性携带有关其状态和历史记录的信息,如任务是否完成、异步方法是否导致异常或已取消以及最终结果是什么。 await 运算符可访问这些属性。

如果等待的任务返回异步方法导致异常,则 await 运算符会重新引发异常。

如果等待的任务返回异步方法取消,则 await 运算符会重新引发 OperationCanceledException

处于故障状态的单个任务可以反映多个异常。 例如,任务可能是调用 Task.WhenAll 的结果。 等待此类任务时,等待操作仅重新引发异常之一。 但是,无法预测重新引发的异常。

有关异步方法中的错误处理的示例,请参阅 try catch。

示例

下面的 Windows 窗体示例阐释如何在异步方法 WaitAsynchronouslyAsync 中使用 await。 将该方法的行为与 WaitSynchronously 的行为进行对比。 如果未向任务应用 await 运算符,WaitSynchronously 就会同步运行,而不管其定义中是否使用了 async 修饰符和在主体中是否调用了 Thread.Sleep

private async void button1_Click(object sender, EventArgs e)  
{  
    //调用异步运行的方法
    string result = await WaitAsynchronouslyAsync();  

    //调用同步运行的方法
    //string result = await WaitSynchronously ();  

    //显示结果
    textBox1.Text += result;  
}  

//以下方法异步运行。 延迟期间UI线程不被阻止。 您可以在Task.Delay运行时移动或调整Form1窗口大小。 
public async Task<string> WaitAsynchronouslyAsync()  
{  
    await Task.Delay(10000);  
    return "Finished";  
}  

//尽管使用异步,但以下方法同步运行。
//由于UI线程被阻止,因此Thread.Sleep正在运行时,无法移动或调整Form1窗口的大小。
public async Task<string> WaitSynchronously()  
{  
    //为System.Threading添加一个using指令
    Thread.Sleep(10000);  
    return "Finished";  
}
<think>我们正在解决C#中'await运算符只能用于异步方法中'的错误。这个错误通常发生在尝试在非异步方法中使用await关键字时。根据引用[1]的解释,await运算符必须用于标记为async的方法中。这是因为await会改变方法的执行流程,使其在等待异步操作完成时不会阻塞线程,而只有异步方法(async修饰)才能支持这种控制流的改变。解决方案步骤:1.确保使用await的方法被标记为async。2.异步方法的返回类型通常是Task,Task<T>或ValueTask<T>(对于性能敏感的场景)。3.如果方法有返回值,则返回Task<T>,其中T是实际返回类型;如果没有返回值,则返回Task。4.注意:异步方法中如果缺少await,编译器会警告(如引用[2][^2]和引用[3][^3]所述),可能导致方法同步运行。错误示例:```csharp//错误:方法没有async修饰符,不能使用awaitpublicvoidMyMethod(){awaitTask.Delay(1000);//这里会报错}```正确做法:将方法标记为async,并返回适当的Task类型。```csharppublicasyncTaskMyMethodAsync(){awaitTask.Delay(1000);}```如果方法有返回值:```csharppublicasyncTask<int>CalculateAsync(){awaitTask.Delay(1000);return42;}```另外,根据引用[3][^3],如果异步方法中没有使用await,编译器会提示“此异步方法缺少await运算符,将以同步方式运行”。因此,确保在异步方法中正确使用await。特殊情况处理:1.如果方法内部没有await,但标记为async,那么它会同步运行(编译器警告)。此时,考虑移除async关键字,或者添加await(例如使用Task.Run来包装同步代码)。2.在顶层方法(如Main方法)中使用await:从C#7.1开始,Main方法可以标记为async,返回Task。例如,控制台应用的Main方法:```csharpstaticasyncTaskMain(string[]args){awaitMyMethodAsync();}```总结:要解决“await运算符只能用于异步方法中”的错误,只需将包含await的方法加上async修饰符,并修改返回类型为Task、Task<T>或ValueTask<T>。注意:异步方法的异常处理也很重要,通常使用try-catch捕获异常。示例:包含异常处理的异步方法```csharppublicasyncTask<string>DownloadStringAsync(stringurl){try{using(varclient=newHttpClient()){returnawaitclient.GetStringAsync(url);}}catch(HttpRequestExceptionex){//处理异常returnstring.Empty;}}```相关问题:1.如何在C#中正确处理异步方法的异常?2.C#异步方法中如何取消长时间运行的任务?3.C#asyncvoid方法的使用场景和注意事项是什么?</think>### C# 中 "await运算符只能用于异步方法中" 错误解决方案 #### 🔍 错误原因分析 根据引用[1][^1]和引用[2][^2],此错误发生在以下情况: 1. 方法未标记 `async` 修饰符时使用了 `await` 2. `async` 方法中缺少必要的 `await` 运算符 3. 尝试在同步方法中直接调用异步操作 #### ✅ 完整解决方案 ##### 1. 添加 `async` 修饰符(基本修复) ```csharp // ❌ 错误:缺少async修饰符 public void GetData() { var result = await client.GetAsync("https://api.example.com"); } // ✅ 正确:添加async修饰符和Task返回类型 public async Task GetDataAsync() { var result = await client.GetAsync("https://api.example.com"); } ``` ##### 2. 正确处理 `async` 方法中的 `await`(引用[3][^3]) ```csharp // ❌ 危险:缺少awaitasync方法(将同步运行) public async Task ProcessData() { HeavyCpuWork(); // 没有await,实际同步执行 } // ✅ 正确方式1:使用await调用异步方法 public async Task ProcessData() { await HeavyCpuWorkAsync(); } // ✅ 正确方式2:使用Task.Run包装CPU密集型工作 public async Task ProcessData() { await Task.Run(() => HeavyCpuWork()); } ``` ##### 3. 入口方法处理(C# 7.1+) ```csharp // 旧版Main方法处理异步 static void Main() { MainAsync().GetAwaiter().GetResult(); } static async Task MainAsync() { await GetDataAsync(); } // ✅ C# 7.1+ 直接支持异步Main static async Task Main(string[] args) { await GetDataAsync(); } ``` ##### 4. 属性/构造函数中的异步处理 ```csharp // ❌ 错误:属性中不能直接使用await public string Data => await FetchData(); // ✅ 正确:通过异步初始化模式 private string _data; public async Task InitializeAsync() { _data = await FetchData(); } ``` #### ⚠️ 重要注意事项 1. **异步方法签名**: - 无返回值:`async Task MethodAsync()` - 有返回值:`async Task<T> MethodAsync()` - 避免 `async void`(仅用于事件处理程序) 2. **警告处理**: > 如引用[3][^3]所述,缺少 `await` 的 `async` 方法会触发编译器警告: > *"此异步方法缺少await运算符,将以同步方式运行"* - 实际需要 `await` 的操作必须添加 `await` - CPU密集型工作应使用 `Task.Run` 3. **调用链一致性**: ```mermaid graph LR A[同步方法] --> B[异步方法] B --> C[使用await调用] C --> D[返回Task/Task<T>] ``` #### 💡 最佳实践总结 1. 检查所有使用 `await` 的方法是否标记为 `async` 2. 确保 `async` 方法中至少有一个 `await` 表达式 3. 将同步CPU密集型工作封装在 `Task.Run` 中 4. 使用 `async Task` 替代 `async void`(事件处理除外) 5. 从C# 7.1开始使用异步 `Main` 方法作为入口点 > 异步编程的核心原则:`async`方法应包含至少一个`await`,否则会退化为同步执行[^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值